beatpath 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2a3814f16d34b25057e169e19a0d389039b27f14
4
+ data.tar.gz: cac724e3ee0104f529936cfb4da331705d5790c2
5
+ SHA512:
6
+ metadata.gz: 4275c9c37e92c67dbdf5bb973c13d906b99b6f199ed4a8397650c46def621e081e5657f2a1d875cefa1ad481503ad3e784b6b80f91cd0960a4f5033b5820b238
7
+ data.tar.gz: 7776cd3a9197c4fab90513058afdd266e5e1f7063dbfbc0fe3f05769fe8ca10d244ce6cd8d1404c955d33d82cdebbd39d08204a2a1b5b048518f12c82bdd0e55
@@ -0,0 +1,19 @@
1
+ .DS_Store
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ *.swp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,218 @@
1
+ AllCops:
2
+ Exclude:
3
+ - .git/**/*
4
+ - tmp/**/*
5
+
6
+ Lint/DuplicateMethods:
7
+ Enabled: true
8
+
9
+ Lint/DeprecatedClassMethods:
10
+ Enabled: true
11
+
12
+ Style/TrailingWhitespace:
13
+ Enabled: true
14
+
15
+ Style/Tab:
16
+ Enabled: true
17
+
18
+ Style/TrailingBlankLines:
19
+ Enabled: true
20
+
21
+ Style/NilComparison:
22
+ Enabled: true
23
+
24
+ Style/NonNilCheck:
25
+ Enabled: true
26
+
27
+ Style/Not:
28
+ Enabled: true
29
+
30
+ Style/RedundantReturn:
31
+ Enabled: true
32
+
33
+ Style/ClassCheck:
34
+ Enabled: true
35
+
36
+ Style/EmptyLines:
37
+ Enabled: true
38
+
39
+ Style/EmptyLiteral:
40
+ Enabled: true
41
+
42
+ Style/Alias:
43
+ Enabled: true
44
+
45
+ Style/MethodCallParentheses:
46
+ Enabled: true
47
+
48
+ Style/MethodDefParentheses:
49
+ Enabled: true
50
+
51
+ Style/SpaceBeforeBlockBraces:
52
+ Enabled: true
53
+
54
+ Style/SpaceInsideBlockBraces:
55
+ Enabled: true
56
+
57
+ Style/SpaceInsideParens:
58
+ Enabled: true
59
+
60
+ Style/DeprecatedHashMethods:
61
+ Enabled: true
62
+
63
+ Style/HashSyntax:
64
+ Enabled: true
65
+
66
+ Style/SpaceInsideHashLiteralBraces:
67
+ Enabled: true
68
+ EnforcedStyle: no_space
69
+
70
+ Style/SpaceInsideBrackets:
71
+ Enabled: true
72
+
73
+ Style/AndOr:
74
+ Enabled: false
75
+
76
+ Style/TrailingCommaInLiteral:
77
+ Enabled: true
78
+
79
+ Style/SpaceBeforeComma:
80
+ Enabled: true
81
+
82
+ Style/SpaceBeforeComment:
83
+ Enabled: true
84
+
85
+ Style/SpaceBeforeSemicolon:
86
+ Enabled: true
87
+
88
+ Style/SpaceAroundBlockParameters:
89
+ Enabled: true
90
+
91
+ Style/SpaceAroundOperators:
92
+ Enabled: true
93
+
94
+ Style/SpaceAfterColon:
95
+ Enabled: true
96
+
97
+ Style/SpaceAfterComma:
98
+ Enabled: true
99
+
100
+ Style/SpaceAroundKeyword:
101
+ Enabled: true
102
+
103
+ Style/SpaceAfterNot:
104
+ Enabled: true
105
+
106
+ Style/SpaceAfterSemicolon:
107
+ Enabled: true
108
+
109
+ Lint/UselessComparison:
110
+ Enabled: true
111
+
112
+ Lint/InvalidCharacterLiteral:
113
+ Enabled: true
114
+
115
+ Lint/LiteralInInterpolation:
116
+ Enabled: true
117
+
118
+ Lint/LiteralInCondition:
119
+ Enabled: true
120
+
121
+ Lint/UnusedBlockArgument:
122
+ Enabled: true
123
+
124
+ Style/VariableInterpolation:
125
+ Enabled: true
126
+
127
+ Style/RedundantSelf:
128
+ Enabled: true
129
+
130
+ Style/ParenthesesAroundCondition:
131
+ Enabled: true
132
+
133
+ Style/WhileUntilDo:
134
+ Enabled: true
135
+
136
+ Style/EmptyLineBetweenDefs:
137
+ Enabled: true
138
+
139
+ Style/EmptyLinesAroundAccessModifier:
140
+ Enabled: true
141
+
142
+ Style/EmptyLinesAroundMethodBody:
143
+ Enabled: true
144
+
145
+ Style/ColonMethodCall:
146
+ Enabled: true
147
+
148
+ Lint/SpaceBeforeFirstArg:
149
+ Enabled: true
150
+
151
+ Lint/UnreachableCode:
152
+ Enabled: true
153
+
154
+ Style/UnlessElse:
155
+ Enabled: true
156
+
157
+ Style/ClassVars:
158
+ Enabled: true
159
+
160
+ Style/StringLiterals:
161
+ Enabled: true
162
+ EnforcedStyle: double_quotes
163
+
164
+ Metrics/CyclomaticComplexity:
165
+ Max: 10
166
+
167
+ Metrics/LineLength:
168
+ Max: 128
169
+
170
+ Metrics/MethodLength:
171
+ Max: 32
172
+
173
+ Metrics/PerceivedComplexity:
174
+ Max: 8
175
+
176
+ # Disabled
177
+
178
+ Style/EvenOdd:
179
+ Enabled: false
180
+
181
+ Style/AsciiComments:
182
+ Enabled: false
183
+
184
+ Style/NumericLiterals:
185
+ Enabled: false
186
+
187
+ Style/UnneededPercentQ:
188
+ Enabled: false
189
+
190
+ Style/SpecialGlobalVars:
191
+ Enabled: false
192
+
193
+ Style/TrivialAccessors:
194
+ Enabled: false
195
+
196
+ Style/PerlBackrefs:
197
+ Enabled: false
198
+
199
+ Metrics/AbcSize:
200
+ Enabled: false
201
+
202
+ Metrics/BlockNesting:
203
+ Enabled: false
204
+
205
+ Metrics/ClassLength:
206
+ Enabled: false
207
+
208
+ Metrics/MethodLength:
209
+ Enabled: false
210
+
211
+ Metrics/ParameterLists:
212
+ Enabled: false
213
+
214
+ Metrics/PerceivedComplexity:
215
+ Enabled: false
216
+
217
+ Style/Documentation:
218
+ Enabled: false
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.2
5
+ before_install: gem install bundler -v 1.13.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in beatpath.gemspec
4
+ gemspec
@@ -0,0 +1,48 @@
1
+ # Beatpath
2
+
3
+ Ruby implementation of the Schulze voting method (https://en.wikipedia.org/wiki/Schulze_method).
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem "beatpath"
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install beatpath
20
+
21
+ ## Usage
22
+
23
+ ```ruby
24
+ # expected input is an array of arrays
25
+ ballots = [
26
+ [55, 34, 56, 76], # each array is a ballot with candidate IDs, in ranked order
27
+ # so, this ballot implies that they preferred candidate ID 55 first, then
28
+ # candidate ID 34 second
29
+ # the candidate ID can be any unique object (symbol, AR record, etc.)
30
+ [55, 56, 76, 34],
31
+ [76, 55, 56, 34],
32
+ [55, 56, 76, 34]
33
+ ]
34
+
35
+ winners = Beatpath::Vote.new(ballots).winners
36
+ # [55, 56, 76, 34] means that candidate ID 55 is in first place, 56 is in second place, 76 in third, and 34 is last
37
+ ```
38
+
39
+ ## Development
40
+
41
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
42
+
43
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
44
+
45
+ ## Contributing
46
+
47
+ Bug reports and pull requests are welcome on GitHub at https://github.com/nickelser/beatpath.
48
+
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "beatpath/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "beatpath"
8
+ spec.version = Beatpath::VERSION
9
+ spec.authors = ["Nick Elser"]
10
+ spec.email = ["nick.elser@gmail.com"]
11
+
12
+ spec.summary = %q{Schulze (Condorcet) voting calculation.}
13
+ spec.homepage = "https://github.com/nickelser/beatpath"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
16
+ f.match(%r{^(test|spec|features)/})
17
+ end
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.13"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "rspec", "~> 3.0"
25
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "beatpath"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,14 @@
1
+ require "beatpath/version"
2
+ require "beatpath/vote_matrix"
3
+
4
+ module Beatpath
5
+ class Vote
6
+ attr_reader :pairwise_matrix, :strongest_paths, :winners
7
+
8
+ def initialize(ballots, candidates = nil)
9
+ @pairwise_matrix = VoteMatrix.pairwise_from_ballots(ballots, candidates)
10
+ @strongest_paths = @pairwise_matrix.floyd_warshall
11
+ @winners = @strongest_paths.winners
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ module Beatpath
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,124 @@
1
+ require "matrix"
2
+ require "set"
3
+
4
+ module Beatpath
5
+ class VoteMatrix < ::Matrix
6
+ # why?
7
+ public :"[]=", :set_element, :set_component
8
+ attr_accessor :vote_candidates
9
+
10
+ def self.zero_with_candidates(candidates = [])
11
+ zeroed = zero(candidates.size)
12
+ zeroed.vote_candidates = candidates
13
+ zeroed
14
+ end
15
+
16
+ def self.pairwise_from_ballots(ballots, candidates = nil)
17
+ if candidates.nil?
18
+ candidates = SortedSet.new
19
+
20
+ # compute distinct candidates
21
+ ballots.each do |b|
22
+ candidates += b
23
+ end
24
+
25
+ candidates = candidates.to_a
26
+ end
27
+
28
+ d = zero_with_candidates(candidates)
29
+
30
+ # compute pairwise rankings from all ballots
31
+ ballots.each do |b|
32
+ # for each ballot, compute which candidates were not voted on
33
+ unvoted_candidates = candidates - b
34
+
35
+ # for each candidate on the given ballot
36
+ b.each_with_index do |c1, idx|
37
+ # compute our internal mapping of the candidate
38
+ c1_idx = candidates.find_index(c1)
39
+
40
+ # for each candidate, c2, that follows this candidate in order of preference,
41
+ # mark c1 as pairwise better than c2
42
+ b[(idx + 1)..-1].each do |c2|
43
+ d[c1_idx, candidates.find_index(c2)] += 1
44
+ end
45
+
46
+ # candidates not mentioned on this ballot are always strictly worse
47
+ # than the candidates on the ballot (c1)
48
+ unvoted_candidates.each do |c2|
49
+ d[c1_idx, candidates.find_index(c2)] += 1
50
+ end
51
+ end
52
+ end
53
+
54
+ d
55
+ end
56
+
57
+ # https://en.wikipedia.org/wiki/Schulze_method
58
+ # Input: self[i,j], the number of voters who prefer candidate i to candidate j.
59
+ # Output: p[i,j], the strength of the strongest path from candidate i to candidate j.
60
+ def floyd_warshall
61
+ p = VoteMatrix.zero_with_candidates(vote_candidates)
62
+
63
+ # for i from 1 to C
64
+ # for j from 1 to C
65
+ # if (i ≠ j) then
66
+ # if (d[i,j] > d[j,i]) then
67
+ # p[i,j] := d[i,j]
68
+ # else
69
+ # p[i,j] := 0
70
+ row_count.times do |i|
71
+ row_count.times do |j|
72
+ next if i == j
73
+
74
+ if self[i, j] > self[j, i]
75
+ p[i, j] = self[i, j]
76
+ else
77
+ p[i, j] = 0
78
+ end
79
+ end
80
+ end
81
+
82
+ # for i from 1 to C
83
+ # for j from 1 to C
84
+ # if (i ≠ j) then
85
+ # for k from 1 to C
86
+ # if (i ≠ k and j ≠ k) then
87
+ # p[j,k] := max ( p[j,k], min ( p[j,i], p[i,k] )
88
+ row_count.times do |i|
89
+ row_count.times do |j|
90
+ next if i == j
91
+
92
+ row_count.times do |k|
93
+ next if i == k || j == k
94
+
95
+ p[j, k] = [p[j, k], [p[j, i], p[i, k]].min].max
96
+ end
97
+ end
98
+ end
99
+
100
+ p
101
+ end
102
+
103
+ def winners
104
+ results = VoteMatrix.zero_with_candidates(vote_candidates)
105
+
106
+ # compute which candidate has the strongest path
107
+ row_count.times do |i|
108
+ row_count.times do |j|
109
+ next if i == j
110
+
111
+ # candidate i is strictly better than j if the strongest path goes through i
112
+ results[i, j] += 1 if self[i, j] > self[j, i]
113
+ end
114
+ end
115
+
116
+ # and finally compute the final ordering of the candidates
117
+ results = Hash[results.row_vectors.each_with_index.map do |r, i|
118
+ [vote_candidates[i], r.inject(:+)]
119
+ end]
120
+
121
+ results.sort_by { |_, v| v }.map(&:first).reverse
122
+ end
123
+ end
124
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: beatpath
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nick Elser
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-04-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.13'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.13'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description:
56
+ email:
57
+ - nick.elser@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".rspec"
64
+ - ".rubocop.yml"
65
+ - ".travis.yml"
66
+ - Gemfile
67
+ - README.md
68
+ - Rakefile
69
+ - beatpath.gemspec
70
+ - bin/console
71
+ - bin/setup
72
+ - lib/beatpath.rb
73
+ - lib/beatpath/version.rb
74
+ - lib/beatpath/vote_matrix.rb
75
+ homepage: https://github.com/nickelser/beatpath
76
+ licenses: []
77
+ metadata: {}
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubyforge_project:
94
+ rubygems_version: 2.5.2
95
+ signing_key:
96
+ specification_version: 4
97
+ summary: Schulze (Condorcet) voting calculation.
98
+ test_files: []