hash_sample 0.8.5 → 0.8.6
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 +4 -4
- data/Gemfile +4 -0
- data/LICENSE +21 -21
- data/README.md +87 -85
- data/Rakefile +160 -155
- data/hash_sample.gemspec +41 -35
- data/lib/hash_sample/version.rb +3 -3
- data/lib/hash_sample.rb +94 -94
- data/spec/hash_sample_spec.rb +168 -168
- data/spec/spec_helper.rb +7 -7
- metadata +47 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 687ec220029dffa020de6baf5fff22728a6e275f0f9fb4d7a404aae3a4242b6c
|
4
|
+
data.tar.gz: 2a11a5e333024cdfa5f956c83394c105d00f77c8d43b17cdfc36aa411d1cc241
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c9f37df2db1c8430edc64bdd6a7ad5e26fa06248a66af51106f8e426e98a8c165cb71c7d71c832f96b94a8c0ddc829cf905dac1336f5e2720366427d8dc2fc3d
|
7
|
+
data.tar.gz: 3ffaa49c402291f6a68fcfe3d590ece00427ac3c4d0e0920a1e8651bb3fecbb2fcfe177129894d8fa047651e1bf4e11b15252099b68536ab49c18f076e149700
|
data/Gemfile
ADDED
data/LICENSE
CHANGED
@@ -1,21 +1,21 @@
|
|
1
|
-
MIT License
|
2
|
-
|
3
|
-
Copyright (c) 2020 Sergey Evstegneiev
|
4
|
-
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
7
|
-
in the Software without restriction, including without limitation the rights
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
10
|
-
furnished to do so, subject to the following conditions:
|
11
|
-
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
13
|
-
copies or substantial portions of the Software.
|
14
|
-
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
-
SOFTWARE.
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2020 Sergey Evstegneiev
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
@@ -1,86 +1,88 @@
|
|
1
|
-
# hash_sample
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
p loaded_die.wchoice
|
17
|
-
p loaded_die.
|
18
|
-
p loaded_die.
|
19
|
-
p loaded_die.wsample
|
20
|
-
p loaded_die.
|
21
|
-
p loaded_die.
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
If the hash
|
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
|
-
|
1
|
+
# hash_sample
|
2
|
+
|
3
|
+
[](https://travis-ci.com/serg123e/hash_sample)
|
4
|
+
|
5
|
+
Implements methods for Hash class for getting weighted random samples with and without replacement, as well as regular random samples
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
gem install hash_sample
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
require 'hash_sample'
|
15
|
+
loaded_die = {'1' => 0.1, '2' => 0.1, '3' => 0.1, '4' => 0.1, '5' => 0.1, '6' => 0.5}
|
16
|
+
p loaded_die.wchoice # "6"
|
17
|
+
p loaded_die.wchoice(1) # ["6"]
|
18
|
+
p loaded_die.wchoice(10) # ["4", "6", "3", "3", "2", "2", "1", "6", "4", "6"]
|
19
|
+
p loaded_die.wsample # 6
|
20
|
+
p loaded_die.wsample(6) # ["6", "3", "2", "4", "1", "5"]
|
21
|
+
p loaded_die.wsample(10) # ["2", "6", "1", "3", "4", "5"]
|
22
|
+
p loaded_die.sample # { '1' => 0.1 }
|
23
|
+
p loaded_die.sample(6) # {'1' => 0.1, '2' => 0.1, '3' => 0.1, '4' => 0.1, '5' => 0.1, '6' => 0.5}
|
24
|
+
```
|
25
|
+
|
26
|
+
## Hash instance methods
|
27
|
+
### hash.sample(n = 1) ⇒ Hash
|
28
|
+
Choose a random key=>value pair or n random pairs from the hash.
|
29
|
+
|
30
|
+
The key=>value pairs are chosen by using random and unique indices in order to ensure that each pair doesn't includes more than once
|
31
|
+
|
32
|
+
If the hash is empty it returns an empty hash.
|
33
|
+
|
34
|
+
If the hash contains less than n unique keys, the copy of whole hash will be returned, none of keys will be lost due to bad luck.
|
35
|
+
|
36
|
+
Returns new Hash containing sample key=>value pairs
|
37
|
+
|
38
|
+
### hash.wchoice ⇒ Object
|
39
|
+
### hash.wchoice(n) ⇒ Array of n samples.
|
40
|
+
Weighted random sampling *with* replacement.
|
41
|
+
|
42
|
+
Choose a random key or n random keys from the hash, according to weights defined in hash values.
|
43
|
+
|
44
|
+
The samples are drawn by using random and replaced by its copy, so they **can be repeated in result**.
|
45
|
+
|
46
|
+
If the hash is empty the first form returns nil and the second form returns an empty array.
|
47
|
+
|
48
|
+
All weights should be Numeric.
|
49
|
+
|
50
|
+
Zero or negative weighs will be ignored.
|
51
|
+
|
52
|
+
{'_' => 9, 'a' => 1}.wchoice(10) # ["_", "a", "_", "_", "_", "_", "_", "_", "_", "_"]
|
53
|
+
|
54
|
+
### hash.wsample ⇒ Object
|
55
|
+
### hash.wsample(n) ⇒ Array of n samples.
|
56
|
+
Weighted random sampling *without* replacement.
|
57
|
+
|
58
|
+
Choose 1 or n *distinct* random keys from the hash, according to weights defined in hash values.
|
59
|
+
Drawn items are not replaced.
|
60
|
+
|
61
|
+
If the hash is empty the first form returns nil and the second form returns an empty array.
|
62
|
+
|
63
|
+
All weights should be Numeric.
|
64
|
+
|
65
|
+
Zero or negative weighs will be ignored.
|
66
|
+
|
67
|
+
{'_' => 9, 'a' => 1}.wchoice(10) # ["_", "a"]
|
68
|
+
|
69
|
+
### hash.wchoices(n = 1) ⇒ Object
|
70
|
+
alias for wchoice
|
71
|
+
|
72
|
+
### hash.wsamples(n = 1) ⇒ Object
|
73
|
+
alias for wsample
|
74
|
+
|
75
|
+
## Contributing
|
76
|
+
|
77
|
+
1. Fork it ( https://github.com/serg123e/hash_sample/fork )
|
78
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
79
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
80
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
81
|
+
5. Create a new Pull Request
|
82
|
+
|
83
|
+
## References
|
84
|
+
|
85
|
+
1. [Efraimidis and Spirakis implementation of random sampling with replacement](https://gist.github.com/O-I/3e0654509dd8057b539a)
|
86
|
+
2. [Weighted Random Sampling (2005; Efraimidis, Spirakis)](https://utopia.duth.gr/~pefraimi/research/data/2007EncOfAlg.pdf)
|
87
|
+
3. [Abandoned Ruby feature request](https://bugs.ruby-lang.org/issues/4247#change-25166)
|
86
88
|
4. [Inspiring example of using max_by for Enumerables with the same algorithm](https://ruby-doc.org/core-2.7.1/Enumerable.html#method-i-max_by)
|
data/Rakefile
CHANGED
@@ -1,156 +1,161 @@
|
|
1
|
-
require 'yard'
|
2
|
-
require 'rake'
|
3
|
-
require 'date'
|
4
|
-
|
5
|
-
#############################################################################
|
6
|
-
#
|
7
|
-
# Helper functions
|
8
|
-
#
|
9
|
-
#############################################################################
|
10
|
-
|
11
|
-
def name
|
12
|
-
"hash_sample"
|
13
|
-
end
|
14
|
-
|
15
|
-
def version
|
16
|
-
line = File.read("lib/#{name}/version.rb")[/^\s*VERSION\s*=\s*.*/]
|
17
|
-
line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
|
18
|
-
end
|
19
|
-
|
20
|
-
# assumes x.y.z all digit version
|
21
|
-
def next_version
|
22
|
-
# x.y.z
|
23
|
-
v = version.split '.'
|
24
|
-
# bump z
|
25
|
-
v[-1] = v[-1].to_i + 1
|
26
|
-
v.join '.'
|
27
|
-
end
|
28
|
-
|
29
|
-
def bump_version
|
30
|
-
old_file = File.read("lib/#{name}/version.rb")
|
31
|
-
old_version_line = old_file[/^\s*VERSION\s*=\s*.*/]
|
32
|
-
new_version = next_version
|
33
|
-
# replace first match of old version with new version
|
34
|
-
old_file.sub!(old_version_line, " VERSION = '#{new_version}'")
|
35
|
-
|
36
|
-
File.write("lib/#{name}/version.rb", old_file)
|
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
|
-
sh "
|
73
|
-
sh "
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
#
|
86
|
-
|
87
|
-
Rake::Task[:
|
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
|
-
replace_header(head, :
|
116
|
-
replace_header(head, :
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
reject { |file| file =~
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
libfiles
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
1
|
+
require 'yard'
|
2
|
+
require 'rake'
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
#############################################################################
|
6
|
+
#
|
7
|
+
# Helper functions
|
8
|
+
#
|
9
|
+
#############################################################################
|
10
|
+
|
11
|
+
def name
|
12
|
+
"hash_sample"
|
13
|
+
end
|
14
|
+
|
15
|
+
def version
|
16
|
+
line = File.read("lib/#{name}/version.rb")[/^\s*VERSION\s*=\s*.*/]
|
17
|
+
line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
|
18
|
+
end
|
19
|
+
|
20
|
+
# assumes x.y.z all digit version
|
21
|
+
def next_version
|
22
|
+
# x.y.z
|
23
|
+
v = version.split '.'
|
24
|
+
# bump z
|
25
|
+
v[-1] = v[-1].to_i + 1
|
26
|
+
v.join '.'
|
27
|
+
end
|
28
|
+
|
29
|
+
def bump_version
|
30
|
+
old_file = File.read("lib/#{name}/version.rb")
|
31
|
+
old_version_line = old_file[/^\s*VERSION\s*=\s*.*/]
|
32
|
+
new_version = next_version
|
33
|
+
# replace first match of old version with new version
|
34
|
+
old_file.sub!(old_version_line, " VERSION = '#{new_version}'")
|
35
|
+
|
36
|
+
File.write("lib/#{name}/version.rb", old_file)
|
37
|
+
new_version
|
38
|
+
end
|
39
|
+
|
40
|
+
def replace_header(head, header_name)
|
41
|
+
head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
|
42
|
+
end
|
43
|
+
|
44
|
+
def gemspec_file
|
45
|
+
"#{name}.gemspec"
|
46
|
+
end
|
47
|
+
|
48
|
+
def gem_files
|
49
|
+
["#{name}-#{version}.gem"]
|
50
|
+
end
|
51
|
+
|
52
|
+
def gemspecs
|
53
|
+
["#{name}.gemspec"]
|
54
|
+
end
|
55
|
+
|
56
|
+
def date
|
57
|
+
Date.today.to_s
|
58
|
+
end
|
59
|
+
#############################################################################
|
60
|
+
#
|
61
|
+
# Custom tasks (add your own tasks here)
|
62
|
+
#
|
63
|
+
#############################################################################
|
64
|
+
|
65
|
+
YARD::Rake::YardocTask.new do |t|
|
66
|
+
end
|
67
|
+
|
68
|
+
desc "Generate RCov test coverage and open in your browser"
|
69
|
+
task :coverage do
|
70
|
+
require 'rcov'
|
71
|
+
sh "rm -fr coverage"
|
72
|
+
sh "rcov test/test_*.rb"
|
73
|
+
sh "open coverage/index.html"
|
74
|
+
end
|
75
|
+
|
76
|
+
desc "Open an irb session preloaded with this library"
|
77
|
+
task :console do
|
78
|
+
sh "irb -r rubygems -r ./lib/#{name}.rb"
|
79
|
+
end
|
80
|
+
|
81
|
+
desc "Update version number and gemspec"
|
82
|
+
task :bump do
|
83
|
+
puts "Updated version to #{bump_version}"
|
84
|
+
# Execute does not invoke dependencies.
|
85
|
+
# Manually invoke gemspec then validate.
|
86
|
+
Rake::Task[:gemspec].execute
|
87
|
+
Rake::Task[:validate].execute
|
88
|
+
end
|
89
|
+
|
90
|
+
desc 'Build gem'
|
91
|
+
task :build => :gemspec do
|
92
|
+
sh "mkdir pkg"
|
93
|
+
gemspecs.each do |gemspec|
|
94
|
+
sh "gem build #{gemspec}"
|
95
|
+
end
|
96
|
+
gem_files.each do |gem_file|
|
97
|
+
sh "mv #{gem_file} pkg"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
desc "Build and install"
|
103
|
+
task :install => :build do
|
104
|
+
sh "gem install --local --no-document pkg/#{name}-#{version}.gem"
|
105
|
+
end
|
106
|
+
|
107
|
+
desc 'Update gemspec'
|
108
|
+
task :gemspec => :validate do
|
109
|
+
# read spec file and split out manifest section
|
110
|
+
spec = File.read(gemspec_file)
|
111
|
+
head, _manifest, tail = spec.split(/\s*# = MANIFEST =\n/)
|
112
|
+
|
113
|
+
# replace name version and date
|
114
|
+
replace_header(head, :name)
|
115
|
+
replace_header(head, :version)
|
116
|
+
replace_header(head, :date)
|
117
|
+
#comment this out if your rubyforge_project has a different name
|
118
|
+
# replace_header(head, :rubyforge_project)
|
119
|
+
|
120
|
+
# determine file list from git ls-files
|
121
|
+
files = `git ls-files`.
|
122
|
+
split("\n").
|
123
|
+
sort.
|
124
|
+
reject { |file| file =~ /^\./ }.
|
125
|
+
reject { |file| file =~ /^(rdoc|pkg|test|Home\.md|\.gitattributes|Guardfile)/ }.
|
126
|
+
map { |file| " #{file}" }.
|
127
|
+
join("\n")
|
128
|
+
|
129
|
+
# piece file back together and write
|
130
|
+
manifest = " s.files = %w(\n#{files}\n )"
|
131
|
+
spec = [head, manifest, tail].join("\n # = MANIFEST =\n")
|
132
|
+
File.open(gemspec_file, 'w') { |io| io.write(spec) }
|
133
|
+
puts "Updated #{gemspec_file}"
|
134
|
+
end
|
135
|
+
|
136
|
+
desc 'Validate lib files and version file'
|
137
|
+
task :validate do
|
138
|
+
libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
|
139
|
+
unless libfiles.empty?
|
140
|
+
puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
|
141
|
+
exit!
|
142
|
+
end
|
143
|
+
unless Dir['VERSION*'].empty?
|
144
|
+
puts "A `VERSION` file at root level violates Gem best practices."
|
145
|
+
exit!
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
desc 'Tag commit and push it'
|
150
|
+
task :tag do
|
151
|
+
sh "git tag v#{version}"
|
152
|
+
sh "git push --tags"
|
153
|
+
end
|
154
|
+
|
155
|
+
begin
|
156
|
+
require 'rspec/core/rake_task'
|
157
|
+
desc "run rspec tests"
|
158
|
+
RSpec::Core::RakeTask.new(:spec)
|
159
|
+
task :default => :spec
|
160
|
+
rescue LoadError
|
156
161
|
end
|
data/hash_sample.gemspec
CHANGED
@@ -1,36 +1,42 @@
|
|
1
|
-
Gem::Specification.new do |s|
|
2
|
-
s.name = 'hash_sample'
|
3
|
-
s.platform = Gem::Platform::RUBY
|
4
|
-
s.authors = ["Sergey Evstegneiev"]
|
5
|
-
s.email = ["serg123e@gmail.com"]
|
6
|
-
s.homepage = 'https://github.com/serg123e/hash_sample'
|
7
|
-
s.summary = %q{Implements multiple sampling methods for Hash class}
|
8
|
-
s.description = %q{
|
9
|
-
|
10
|
-
s.add_development_dependency "rspec", "~> 3.5"
|
11
|
-
s.add_development_dependency "
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
s.
|
16
|
-
|
17
|
-
|
18
|
-
s.
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
s.
|
23
|
-
|
24
|
-
s.
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'hash_sample'
|
3
|
+
s.platform = Gem::Platform::RUBY
|
4
|
+
s.authors = ["Sergey Evstegneiev"]
|
5
|
+
s.email = ["serg123e@gmail.com"]
|
6
|
+
s.homepage = 'https://github.com/serg123e/hash_sample'
|
7
|
+
s.summary = %q{Implements multiple sampling methods for Hash class}
|
8
|
+
s.description = %q{Implements methods for Hash class for getting weighted random samples with and without replacement, as well as regular random samples}
|
9
|
+
|
10
|
+
s.add_development_dependency "rspec", "~> 3.5"
|
11
|
+
s.add_development_dependency "simplecov", "~> 0.12"
|
12
|
+
s.add_development_dependency "rspec-simplecov", "~> 0.2"
|
13
|
+
|
14
|
+
s.add_development_dependency "rake", "~> 13"
|
15
|
+
s.add_development_dependency "yard", "~> 0.9"
|
16
|
+
|
17
|
+
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.required_ruby_version = '>= 2.4'
|
21
|
+
|
22
|
+
s.date = '2020-05-01'
|
23
|
+
s.version = '0.8.6'
|
24
|
+
s.license = 'MIT'
|
25
|
+
|
26
|
+
s.rdoc_options = ['--charset=UTF-8']
|
27
|
+
s.extra_rdoc_files = %w(README.md LICENSE)
|
28
|
+
# = MANIFEST =
|
29
|
+
s.files = %w(
|
30
|
+
Gemfile
|
31
|
+
LICENSE
|
32
|
+
README.md
|
33
|
+
Rakefile
|
34
|
+
hash_sample.gemspec
|
35
|
+
lib/hash_sample.rb
|
36
|
+
lib/hash_sample/version.rb
|
37
|
+
spec/hash_sample_spec.rb
|
38
|
+
spec/spec_helper.rb
|
39
|
+
)
|
40
|
+
# = MANIFEST =
|
41
|
+
s.test_files = s.files.select { |path| path =~ /^test\/test_.*\.rb/ }
|
36
42
|
end
|
data/lib/hash_sample/version.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
module HashSample
|
2
|
-
# gem version
|
3
|
-
VERSION = '0.8.
|
1
|
+
module HashSample
|
2
|
+
# gem version
|
3
|
+
VERSION = '0.8.6'
|
4
4
|
end
|
data/lib/hash_sample.rb
CHANGED
@@ -1,94 +1,94 @@
|
|
1
|
-
# monkey-patched Hash module
|
2
|
-
class Hash
|
3
|
-
##
|
4
|
-
# Choose a random key=>value pair or *n* random pairs from the hash.
|
5
|
-
#
|
6
|
-
# @return [Hash] new Hash containing sample key=>value pairs
|
7
|
-
#
|
8
|
-
# The elements are chosen by using random and unique indices in order to ensure that each element doesn't includes more than once.
|
9
|
-
# If the hash is empty it returns an empty hash.
|
10
|
-
# If the hash contains less than *n* unique keys, the copy of whole hash will be returned, none of keys will be lost.
|
11
|
-
def sample(n = 1)
|
12
|
-
to_a.sample(n).to_h
|
13
|
-
end
|
14
|
-
|
15
|
-
###
|
16
|
-
# alias for wchoice
|
17
|
-
def wchoices(*args)
|
18
|
-
wchoice(*args)
|
19
|
-
end
|
20
|
-
|
21
|
-
##
|
22
|
-
# Choose 1 or n random keys from the hash, according to weights defined in hash values
|
23
|
-
# (weighted random sampling *with* *replacement*)
|
24
|
-
#
|
25
|
-
# @overload wchoice
|
26
|
-
# @return [Object] one sample object
|
27
|
-
# @overload wchoice(n)
|
28
|
-
# @param n [Integer] number of samples to be returned
|
29
|
-
# @return [Array] Array of n samples
|
30
|
-
#
|
31
|
-
# The keys are chosen by using random according to its weights and *can* *be* *repeated* *in* *result*.
|
32
|
-
# If the hash is empty the first form returns nil and the second form returns an empty array.
|
33
|
-
# All weights should be Numeric.
|
34
|
-
# Zero or negative weighs will be ignored.
|
35
|
-
#
|
36
|
-
# ===== Example
|
37
|
-
#
|
38
|
-
# p {'_' => 9, 'a' => 1}.wchoice(10) # ["_", "a", "_", "_", "_", "_", "_", "_", "_", "_"]
|
39
|
-
#
|
40
|
-
def wchoice(*args)
|
41
|
-
_check_weighted_params
|
42
|
-
n = args.first || 1
|
43
|
-
res = []
|
44
|
-
n.times do
|
45
|
-
tmp = max_by { |_, weight| weight.positive? ? rand**(1.0 / weight) : 0 }
|
46
|
-
res << tmp.first unless tmp.nil?
|
47
|
-
end
|
48
|
-
return args.empty? ? res.first : res
|
49
|
-
end
|
50
|
-
|
51
|
-
# internal method to validate parameters
|
52
|
-
def _check_weighted_params(*_args)
|
53
|
-
sum_weights = 0
|
54
|
-
each_value do |v|
|
55
|
-
raise ArgumentError, "All weights should be numeric unlike #{v}" unless v.is_a? Numeric
|
56
|
-
|
57
|
-
sum_weights += v if v.positive?
|
58
|
-
end
|
59
|
-
|
60
|
-
raise ArgumentError, "At least one weight should be > 0" unless sum_weights.positive? || empty?
|
61
|
-
end
|
62
|
-
|
63
|
-
##
|
64
|
-
# Choose 1 or n *distinct* random keys from the hash, according to weights defined in hash values
|
65
|
-
# (weighted random sampling *without* *replacement*)
|
66
|
-
#
|
67
|
-
# @overload wsample
|
68
|
-
# @return [Object] one sample object
|
69
|
-
# @overload wsample(n)
|
70
|
-
# @param n [Integer] number of samples to be returned
|
71
|
-
# @return [Array] Array of n or sometimes less than n samples
|
72
|
-
#
|
73
|
-
# When there are no sufficient distinct samples to return, the result will contain less than n samples
|
74
|
-
# If the hash is empty the first form returns nil and the second form returns an empty array.
|
75
|
-
# All weights should be Numeric.
|
76
|
-
# Zero or negative weighs will be ignored.
|
77
|
-
#
|
78
|
-
# ===== Example
|
79
|
-
#
|
80
|
-
# p {'_' => 9, 'a' => 1}.wsample(10) # ["_", "a"]
|
81
|
-
#
|
82
|
-
def wsample(*args)
|
83
|
-
_check_weighted_params
|
84
|
-
n = args.first || 1
|
85
|
-
res = max_by(n) { |_, weight| weight.positive? ? rand**(1.0 / weight) : 0 }.map(&:first)
|
86
|
-
return args.empty? ? res.first : res
|
87
|
-
end
|
88
|
-
|
89
|
-
###
|
90
|
-
# alias for wsample
|
91
|
-
def wsamples(*args)
|
92
|
-
wsample(*args)
|
93
|
-
end
|
94
|
-
end
|
1
|
+
# monkey-patched Hash module
|
2
|
+
class Hash
|
3
|
+
##
|
4
|
+
# Choose a random key=>value pair or *n* random pairs from the hash.
|
5
|
+
#
|
6
|
+
# @return [Hash] new Hash containing sample key=>value pairs
|
7
|
+
#
|
8
|
+
# The elements are chosen by using random and unique indices in order to ensure that each element doesn't includes more than once.
|
9
|
+
# If the hash is empty it returns an empty hash.
|
10
|
+
# If the hash contains less than *n* unique keys, the copy of whole hash will be returned, none of keys will be lost.
|
11
|
+
def sample(n = 1)
|
12
|
+
to_a.sample(n).to_h
|
13
|
+
end
|
14
|
+
|
15
|
+
###
|
16
|
+
# alias for wchoice
|
17
|
+
def wchoices(*args)
|
18
|
+
wchoice(*args)
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Choose 1 or n random keys from the hash, according to weights defined in hash values
|
23
|
+
# (weighted random sampling *with* *replacement*)
|
24
|
+
#
|
25
|
+
# @overload wchoice
|
26
|
+
# @return [Object] one sample object
|
27
|
+
# @overload wchoice(n)
|
28
|
+
# @param n [Integer] number of samples to be returned
|
29
|
+
# @return [Array] Array of n samples
|
30
|
+
#
|
31
|
+
# The keys are chosen by using random according to its weights and *can* *be* *repeated* *in* *result*.
|
32
|
+
# If the hash is empty the first form returns nil and the second form returns an empty array.
|
33
|
+
# All weights should be Numeric.
|
34
|
+
# Zero or negative weighs will be ignored.
|
35
|
+
#
|
36
|
+
# ===== Example
|
37
|
+
#
|
38
|
+
# p {'_' => 9, 'a' => 1}.wchoice(10) # ["_", "a", "_", "_", "_", "_", "_", "_", "_", "_"]
|
39
|
+
#
|
40
|
+
def wchoice(*args)
|
41
|
+
_check_weighted_params
|
42
|
+
n = args.first || 1
|
43
|
+
res = []
|
44
|
+
n.times do
|
45
|
+
tmp = max_by { |_, weight| weight.positive? ? rand**(1.0 / weight) : 0 }
|
46
|
+
res << tmp.first unless tmp.nil?
|
47
|
+
end
|
48
|
+
return args.empty? ? res.first : res
|
49
|
+
end
|
50
|
+
|
51
|
+
# internal method to validate parameters
|
52
|
+
def _check_weighted_params(*_args)
|
53
|
+
sum_weights = 0
|
54
|
+
each_value do |v|
|
55
|
+
raise ArgumentError, "All weights should be numeric unlike #{v}" unless v.is_a? Numeric
|
56
|
+
|
57
|
+
sum_weights += v if v.positive?
|
58
|
+
end
|
59
|
+
|
60
|
+
raise ArgumentError, "At least one weight should be > 0" unless sum_weights.positive? || empty?
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# Choose 1 or n *distinct* random keys from the hash, according to weights defined in hash values
|
65
|
+
# (weighted random sampling *without* *replacement*)
|
66
|
+
#
|
67
|
+
# @overload wsample
|
68
|
+
# @return [Object] one sample object
|
69
|
+
# @overload wsample(n)
|
70
|
+
# @param n [Integer] number of samples to be returned
|
71
|
+
# @return [Array] Array of n or sometimes less than n samples
|
72
|
+
#
|
73
|
+
# When there are no sufficient distinct samples to return, the result will contain less than n samples
|
74
|
+
# If the hash is empty the first form returns nil and the second form returns an empty array.
|
75
|
+
# All weights should be Numeric.
|
76
|
+
# Zero or negative weighs will be ignored.
|
77
|
+
#
|
78
|
+
# ===== Example
|
79
|
+
#
|
80
|
+
# p {'_' => 9, 'a' => 1}.wsample(10) # ["_", "a"]
|
81
|
+
#
|
82
|
+
def wsample(*args)
|
83
|
+
_check_weighted_params
|
84
|
+
n = args.first || 1
|
85
|
+
res = max_by(n) { |_, weight| weight.positive? ? rand**(1.0 / weight) : 0 }.map(&:first)
|
86
|
+
return args.empty? ? res.first : res
|
87
|
+
end
|
88
|
+
|
89
|
+
###
|
90
|
+
# alias for wsample
|
91
|
+
def wsamples(*args)
|
92
|
+
wsample(*args)
|
93
|
+
end
|
94
|
+
end
|
data/spec/hash_sample_spec.rb
CHANGED
@@ -1,168 +1,168 @@
|
|
1
|
-
# require 'lib/core_ext.rb'
|
2
|
-
|
3
|
-
#
|
4
|
-
# Specs
|
5
|
-
#
|
6
|
-
describe 'Hash#sample' do
|
7
|
-
describe 'when specified parameter n>1' do
|
8
|
-
it 'returns new Hash with specified number of unique key=>value samples' do
|
9
|
-
h = { 'a' => 'b', 'b' => 'b', 'c' => 'b' }
|
10
|
-
expect(h.sample(3)).to eq h
|
11
|
-
end
|
12
|
-
end
|
13
|
-
describe 'when specified parameter n> number of unique keys' do
|
14
|
-
it 'returns new Hash only with unique key=>value samples' do
|
15
|
-
h = { 'a' => 'b', 'b' => 'b', 'c' => 'b' }
|
16
|
-
expect(h.sample(10)).to eq h
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
describe 'when specified parameter n> number of unique keys' do
|
21
|
-
it 'keys can not be lost because of bad luck' do
|
22
|
-
h = { 'a' => 'b', 'b' => 'b', 'c' => 'b' }
|
23
|
-
min = h.keys.length
|
24
|
-
100.times do
|
25
|
-
min = [h.sample(4).keys.length, min].min
|
26
|
-
end
|
27
|
-
expect(min).to be 3
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
describe 'when specified parameter n==1' do
|
32
|
-
it 'returns new Hash with 1 random key=>value sample' do
|
33
|
-
h = { 'a' => 'b', 'b' => 'b', 'c' => 'b' }
|
34
|
-
expect(h.sample(1).keys.length).to eq 1
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
%w[wchoice wsample].each do |weighted_method|
|
40
|
-
describe 'plural form of method' do
|
41
|
-
weighted_methods = weighted_method + "s"
|
42
|
-
it 'can be used' do
|
43
|
-
expect({}).to respond_to(weighted_methods)
|
44
|
-
end
|
45
|
-
it 'works as expected without args' do
|
46
|
-
expect({ 'a' => 1 }.send(weighted_methods)).to eq 'a'
|
47
|
-
end
|
48
|
-
it 'works as expected with args' do
|
49
|
-
expect({ 'a' => 1 }.send(weighted_methods, 1)).to eq ['a']
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
describe "Hash\##{weighted_method}" do
|
54
|
-
it 'returns weighted sample key from all keys with respect of its weights' do
|
55
|
-
s = { 1 => 90, 2 => 10 }
|
56
|
-
freq = Hash.new(0)
|
57
|
-
1000.times { freq[s.send(weighted_method)] += 1 }
|
58
|
-
expect(freq[1]).to be_between(800, 999)
|
59
|
-
expect(freq[2]).to be_between(1, 200)
|
60
|
-
end
|
61
|
-
|
62
|
-
describe 'when weights are equal' do
|
63
|
-
it 'it should returns equal parts of samples' do
|
64
|
-
res = 1.upto(100_000).to_a.map { { +1 => 50, -1 => 50 }.send(weighted_method) }
|
65
|
-
expect(res.sum).to be_between(-1000, 1000) # +-1% bias
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
describe 'when Hash is empty' do
|
70
|
-
it 'returns nil' do
|
71
|
-
expect({}.send(weighted_method)).to be_nil
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
describe 'when weights are Float' do
|
76
|
-
it 'returns a value as expected' do
|
77
|
-
expect([1, 2].include?({ 1 => 0.1, 2 => 0.9 }.send(weighted_method))).to be true
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
describe 'when some weights are negative' do
|
82
|
-
it 'does not sample that key' do
|
83
|
-
100.times { expect({ 'a' => -1, 'b' => 2 }.send(weighted_method)).to eq 'b' }
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
describe 'when weight contains zero' do
|
88
|
-
it 'returns non-zero weighted element' do
|
89
|
-
10.times do
|
90
|
-
expect({ 1 => 0, 2 => 1, 3 => 0 }.send(weighted_method)).to eq 2
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
describe 'when weight is non-numeric' do
|
96
|
-
it 'raises ArgumentError' do
|
97
|
-
expect { { 1 => 'asd', 2 => 2 }.send(weighted_method) }.to raise_error(ArgumentError)
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
describe 'when all weights are zero' do
|
102
|
-
it 'raises ArgumentError' do
|
103
|
-
expect { { 1 => 0, 2 => 0 }.send(weighted_method) }.to raise_error(ArgumentError)
|
104
|
-
end
|
105
|
-
# @todo do not raise error when all weights are zero
|
106
|
-
# xit 'returns empty array' do
|
107
|
-
# h = { 'a'=>0, 'b'=>0, 'c'=>0 }
|
108
|
-
# expect( h.wchoice(10) ).to eq []
|
109
|
-
# end
|
110
|
-
end
|
111
|
-
|
112
|
-
describe 'when hash is empty' do
|
113
|
-
it 'returns empty array or nil' do
|
114
|
-
expect({}.send(weighted_method, 10)).to eq []
|
115
|
-
expect({}.send(weighted_method)).to eq nil
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
describe 'when specified parameter n>1' do
|
120
|
-
it 'returns array of sample keys 2' do
|
121
|
-
100.times { expect({ 1 => 1, 2 => 0.01, 3 => 0.0000001 }.wchoice(3).length).to be 3 }
|
122
|
-
end
|
123
|
-
end
|
124
|
-
describe 'when specified parameter n==1' do
|
125
|
-
subject { { '1' => 1, '2' => 1, '3' => 1 }.wchoice(1) }
|
126
|
-
it 'returns array of one key' do
|
127
|
-
expect(subject).to be_kind_of(Array)
|
128
|
-
expect(subject.length).to be 1
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
describe 'should work with complex Objetcts as keys' do
|
133
|
-
subject { { %w[asd zxf] => 1, %w[asd bsd] => 1, %w[asd dsf] => 1 }.wchoice }
|
134
|
-
it 'returns array of one key' do
|
135
|
-
expect(subject).to be_kind_of(Array)
|
136
|
-
expect(subject.length).to be 2
|
137
|
-
expect(subject).to include 'asd'
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
describe 'when specified parameter n is greater than number of unique keys' do
|
142
|
-
it 'returns array with exactly n key samples, repeating some of them' do
|
143
|
-
h = { 'a' => 1, 'b' => 1, 'c' => 1 }
|
144
|
-
expect(h.wchoice(10).length).to eq 10
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
describe 'Hash#wchoice' do
|
151
|
-
describe 'when specified parameter n>1' do
|
152
|
-
it 'returns array of n sample keys' do
|
153
|
-
expect({ 'a' => 1 }.wchoice(2)).to eq %w[a a]
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
describe 'Hash#wsample' do
|
159
|
-
describe 'when specified parameter n>1' do
|
160
|
-
it 'returns array of unique keys' do
|
161
|
-
expect({ 'a' => 1 }.wsample(2)).to eq ['a']
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
it 'returned objects are not repeated' do
|
166
|
-
expect({ '_' => 9, 'a' => 1 }.wsample(10).sort).to eq %w[_ a]
|
167
|
-
end
|
168
|
-
end
|
1
|
+
# require 'lib/core_ext.rb'
|
2
|
+
|
3
|
+
#
|
4
|
+
# Specs
|
5
|
+
#
|
6
|
+
describe 'Hash#sample' do
|
7
|
+
describe 'when specified parameter n>1' do
|
8
|
+
it 'returns new Hash with specified number of unique key=>value samples' do
|
9
|
+
h = { 'a' => 'b', 'b' => 'b', 'c' => 'b' }
|
10
|
+
expect(h.sample(3)).to eq h
|
11
|
+
end
|
12
|
+
end
|
13
|
+
describe 'when specified parameter n> number of unique keys' do
|
14
|
+
it 'returns new Hash only with unique key=>value samples' do
|
15
|
+
h = { 'a' => 'b', 'b' => 'b', 'c' => 'b' }
|
16
|
+
expect(h.sample(10)).to eq h
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe 'when specified parameter n> number of unique keys' do
|
21
|
+
it 'keys can not be lost because of bad luck' do
|
22
|
+
h = { 'a' => 'b', 'b' => 'b', 'c' => 'b' }
|
23
|
+
min = h.keys.length
|
24
|
+
100.times do
|
25
|
+
min = [h.sample(4).keys.length, min].min
|
26
|
+
end
|
27
|
+
expect(min).to be 3
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe 'when specified parameter n==1' do
|
32
|
+
it 'returns new Hash with 1 random key=>value sample' do
|
33
|
+
h = { 'a' => 'b', 'b' => 'b', 'c' => 'b' }
|
34
|
+
expect(h.sample(1).keys.length).to eq 1
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
%w[wchoice wsample].each do |weighted_method|
|
40
|
+
describe 'plural form of method' do
|
41
|
+
weighted_methods = weighted_method + "s"
|
42
|
+
it 'can be used' do
|
43
|
+
expect({}).to respond_to(weighted_methods)
|
44
|
+
end
|
45
|
+
it 'works as expected without args' do
|
46
|
+
expect({ 'a' => 1 }.send(weighted_methods)).to eq 'a'
|
47
|
+
end
|
48
|
+
it 'works as expected with args' do
|
49
|
+
expect({ 'a' => 1 }.send(weighted_methods, 1)).to eq ['a']
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "Hash\##{weighted_method}" do
|
54
|
+
it 'returns weighted sample key from all keys with respect of its weights' do
|
55
|
+
s = { 1 => 90, 2 => 10 }
|
56
|
+
freq = Hash.new(0)
|
57
|
+
1000.times { freq[s.send(weighted_method)] += 1 }
|
58
|
+
expect(freq[1]).to be_between(800, 999)
|
59
|
+
expect(freq[2]).to be_between(1, 200)
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'when weights are equal' do
|
63
|
+
it 'it should returns equal parts of samples' do
|
64
|
+
res = 1.upto(100_000).to_a.map { { +1 => 50, -1 => 50 }.send(weighted_method) }
|
65
|
+
expect(res.sum).to be_between(-1000, 1000) # +-1% bias
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe 'when Hash is empty' do
|
70
|
+
it 'returns nil' do
|
71
|
+
expect({}.send(weighted_method)).to be_nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe 'when weights are Float' do
|
76
|
+
it 'returns a value as expected' do
|
77
|
+
expect([1, 2].include?({ 1 => 0.1, 2 => 0.9 }.send(weighted_method))).to be true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe 'when some weights are negative' do
|
82
|
+
it 'does not sample that key' do
|
83
|
+
100.times { expect({ 'a' => -1, 'b' => 2 }.send(weighted_method)).to eq 'b' }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe 'when weight contains zero' do
|
88
|
+
it 'returns non-zero weighted element' do
|
89
|
+
10.times do
|
90
|
+
expect({ 1 => 0, 2 => 1, 3 => 0 }.send(weighted_method)).to eq 2
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe 'when weight is non-numeric' do
|
96
|
+
it 'raises ArgumentError' do
|
97
|
+
expect { { 1 => 'asd', 2 => 2 }.send(weighted_method) }.to raise_error(ArgumentError)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe 'when all weights are zero' do
|
102
|
+
it 'raises ArgumentError' do
|
103
|
+
expect { { 1 => 0, 2 => 0 }.send(weighted_method) }.to raise_error(ArgumentError)
|
104
|
+
end
|
105
|
+
# @todo do not raise error when all weights are zero
|
106
|
+
# xit 'returns empty array' do
|
107
|
+
# h = { 'a'=>0, 'b'=>0, 'c'=>0 }
|
108
|
+
# expect( h.wchoice(10) ).to eq []
|
109
|
+
# end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe 'when hash is empty' do
|
113
|
+
it 'returns empty array or nil' do
|
114
|
+
expect({}.send(weighted_method, 10)).to eq []
|
115
|
+
expect({}.send(weighted_method)).to eq nil
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe 'when specified parameter n>1' do
|
120
|
+
it 'returns array of sample keys 2' do
|
121
|
+
100.times { expect({ 1 => 1, 2 => 0.01, 3 => 0.0000001 }.wchoice(3).length).to be 3 }
|
122
|
+
end
|
123
|
+
end
|
124
|
+
describe 'when specified parameter n==1' do
|
125
|
+
subject { { '1' => 1, '2' => 1, '3' => 1 }.wchoice(1) }
|
126
|
+
it 'returns array of one key' do
|
127
|
+
expect(subject).to be_kind_of(Array)
|
128
|
+
expect(subject.length).to be 1
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe 'should work with complex Objetcts as keys' do
|
133
|
+
subject { { %w[asd zxf] => 1, %w[asd bsd] => 1, %w[asd dsf] => 1 }.wchoice }
|
134
|
+
it 'returns array of one key' do
|
135
|
+
expect(subject).to be_kind_of(Array)
|
136
|
+
expect(subject.length).to be 2
|
137
|
+
expect(subject).to include 'asd'
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe 'when specified parameter n is greater than number of unique keys' do
|
142
|
+
it 'returns array with exactly n key samples, repeating some of them' do
|
143
|
+
h = { 'a' => 1, 'b' => 1, 'c' => 1 }
|
144
|
+
expect(h.wchoice(10).length).to eq 10
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe 'Hash#wchoice' do
|
151
|
+
describe 'when specified parameter n>1' do
|
152
|
+
it 'returns array of n sample keys' do
|
153
|
+
expect({ 'a' => 1 }.wchoice(2)).to eq %w[a a]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
describe 'Hash#wsample' do
|
159
|
+
describe 'when specified parameter n>1' do
|
160
|
+
it 'returns array of unique keys' do
|
161
|
+
expect({ 'a' => 1 }.wsample(2)).to eq ['a']
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'returned objects are not repeated' do
|
166
|
+
expect({ '_' => 9, 'a' => 1 }.wsample(10).sort).to eq %w[_ a]
|
167
|
+
end
|
168
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
require 'simplecov'
|
2
|
-
require 'rspec/simplecov'
|
3
|
-
|
4
|
-
SimpleCov.minimum_coverage 100
|
5
|
-
SimpleCov.start
|
6
|
-
|
7
|
-
require 'hash_sample'
|
1
|
+
require 'simplecov'
|
2
|
+
require 'rspec/simplecov'
|
3
|
+
|
4
|
+
SimpleCov.minimum_coverage 100
|
5
|
+
SimpleCov.start
|
6
|
+
|
7
|
+
require 'hash_sample'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hash_sample
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sergey Evstegneiev
|
@@ -24,6 +24,34 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '3.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: simplecov
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.12'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.12'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec-simplecov
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.2'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.2'
|
27
55
|
- !ruby/object:Gem::Dependency
|
28
56
|
name: rake
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,8 +66,22 @@ dependencies:
|
|
38
66
|
- - "~>"
|
39
67
|
- !ruby/object:Gem::Version
|
40
68
|
version: '13'
|
41
|
-
|
42
|
-
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: yard
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.9'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.9'
|
83
|
+
description: Implements methods for Hash class for getting weighted random samples
|
84
|
+
with and without replacement, as well as regular random samples
|
43
85
|
email:
|
44
86
|
- serg123e@gmail.com
|
45
87
|
executables: []
|
@@ -48,6 +90,7 @@ extra_rdoc_files:
|
|
48
90
|
- README.md
|
49
91
|
- LICENSE
|
50
92
|
files:
|
93
|
+
- Gemfile
|
51
94
|
- LICENSE
|
52
95
|
- README.md
|
53
96
|
- Rakefile
|
@@ -77,7 +120,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
77
120
|
version: '0'
|
78
121
|
requirements: []
|
79
122
|
rubyforge_project:
|
80
|
-
rubygems_version: 2.7.
|
123
|
+
rubygems_version: 2.7.7
|
81
124
|
signing_key:
|
82
125
|
specification_version: 4
|
83
126
|
summary: Implements multiple sampling methods for Hash class
|