beatpath 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []