schulze-vote 2.0.3 → 2.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 07f4360d75e0227e1074c3bdb3ecbb47cf692759
4
- data.tar.gz: b79fb40072365731ae79c09ee7c9013043467af6
3
+ metadata.gz: 0f1a89377c52c746f04598f5af4202130d6276e4
4
+ data.tar.gz: 55fab398da85310889bee1304c3b099d9a6a6554
5
5
  SHA512:
6
- metadata.gz: 78bcc2f6faade7f90a72bdd46cbfc059d4f1921be199dd5ce8ab73943cc1e9d80d293f820feba645e5749afb7c858641d721dbf250cefbad4fbcb42947124234
7
- data.tar.gz: 7b72e21f89120d106290e6a4f0b8f64db88210e7f5fea5fbf427b1af182948ab9fa3a74c50e53a6fb80ecc3113b3fc65e5015f014e9e5a5c73f2e3000ec1427b
6
+ metadata.gz: 0ee33a934d33321ea22e2f35bad75fa1c9b462b0b4e9c141b7e0709eabe8a3d547fba852526b70a9de395afa12fa55a30b3e4b60dc553b3aeaf95cb2e96a8fee
7
+ data.tar.gz: b7475ff5206ebd7f19166aaf89a4cb88c3e37bd7a2ae15e60d3b4b7b35bc91cab5da1e56541b7feba5b1aebf568c805bbf2e9864d2bd7bb0acfbf7912aa2e289
@@ -0,0 +1,6 @@
1
+ # encoding: UTF-8
2
+ module Vote
3
+ module Condorcet
4
+ autoload :Schulze, 'vote/condorcet/schulze'
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ module Vote
2
+ module Condorcet
3
+ module Schulze
4
+ autoload :Input, 'vote/condorcet/schulze/input'
5
+ autoload :Basic, 'vote/condorcet/schulze/basic'
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,188 @@
1
+ module Vote
2
+ module Condorcet
3
+ module Schulze
4
+ class Basic
5
+ # All-in-One class method to get a calculated SchulzeBasic object
6
+ def self.do(vote_matrix, candidate_count = nil)
7
+ instance = new
8
+ instance.load vote_matrix, candidate_count
9
+ instance.run
10
+ instance
11
+ end
12
+
13
+ def load(vote_matrix, candidate_count = nil)
14
+ input = if vote_matrix.is_a?(Vote::Condorcet::Schulze::Input)
15
+ vote_matrix
16
+ else
17
+ Vote::Condorcet::Schulze::Input.new(
18
+ vote_matrix,
19
+ candidate_count
20
+ )
21
+ end
22
+ @vote_matrix = input.matrix
23
+ @candidate_count = input.candidates
24
+ @vote_count = input.voters
25
+ self
26
+ end
27
+
28
+ def run
29
+ play
30
+ result
31
+ calculate_winners
32
+ rank
33
+ end
34
+
35
+ attr_reader :vote_matrix
36
+
37
+ attr_reader :play_matrix
38
+
39
+ attr_reader :result_matrix
40
+
41
+ def ranks
42
+ @ranking
43
+ end
44
+
45
+ def voters
46
+ @vote_count
47
+ end
48
+
49
+ # return all possible solutions to the votation
50
+ attr_reader :winners_array
51
+
52
+ def classifications
53
+ @classifications ||= calculate_classifications
54
+ end
55
+
56
+ private
57
+
58
+ def play
59
+ @play_matrix = ::Matrix.scalar(@candidate_count, 0).extend(Vote::Matrix)
60
+ # step 1: find matches with wins
61
+ @candidate_count.times do |i|
62
+ @candidate_count.times do |j|
63
+ next if i == j
64
+ if @vote_matrix[i, j] > @vote_matrix[j, i]
65
+ @play_matrix[i, j] = @vote_matrix[i, j]
66
+ else
67
+ @play_matrix[i, j] = 0
68
+ end
69
+ end
70
+ end
71
+
72
+ # step 2: find strongest paths
73
+ @candidate_count.times do |i|
74
+ @candidate_count.times do |j|
75
+ next if i == j
76
+ @candidate_count.times do |k|
77
+ next if (i == k) || (j == k)
78
+ @play_matrix[j, k] = [
79
+ @play_matrix[j, k],
80
+ [@play_matrix[j, i], @play_matrix[i, k]].min
81
+ ].max
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ def result
88
+ @result_matrix = ::Matrix.scalar(@candidate_count, 0).extend(Vote::Matrix)
89
+ @result_matrix.each_with_index do |e, x, y|
90
+ next if x == y
91
+ @result_matrix[x, y] = e + 1 if @play_matrix[x, y] > @play_matrix[y, x]
92
+ end
93
+ end
94
+
95
+ def calculate_winners
96
+ @winners_array = Array.new(@candidate_count, 0)
97
+ @winners_array.each_with_index do |_el, idx|
98
+ row = @play_matrix.row(idx)
99
+ column = @play_matrix.column(idx)
100
+ if row.each_with_index.all? { |r, index| r >= column[index] }
101
+ @winners_array[idx] = 1
102
+ end
103
+ end
104
+ end
105
+
106
+ def rank
107
+ @ranking = @result_matrix.
108
+ row_vectors.map { |e| e.inject(0) { |s, v| s += v } }
109
+ end
110
+
111
+ # you should call calculate_winners first
112
+ def calculate_potential_winners
113
+ @potential_winners = []
114
+ winners_array.each_with_index do |val, idx|
115
+ @potential_winners << idx if val > 0
116
+ end
117
+ @potential_winners
118
+ end
119
+
120
+ def calculate_beat_couples
121
+ @beat_couples = []
122
+ ranks.each_with_index do |_val, idx|
123
+ ranks.each_with_index do |_val2, idx2|
124
+ next if idx == idx2
125
+ if play_matrix[idx, idx2] > play_matrix[idx2, idx]
126
+ @beat_couples << [idx, idx2]
127
+ end
128
+ end
129
+ end
130
+ @beat_couples
131
+ end
132
+
133
+ def rank_element(el)
134
+ rank = 0
135
+ rank -= 100 if @potential_winners.include?(el)
136
+ @beat_couples.each do |b|
137
+ rank -= 1 if b[0] == el
138
+ end
139
+ rank
140
+ end
141
+
142
+ def calculate_classifications
143
+ calculate_potential_winners
144
+ calculate_beat_couples
145
+
146
+ start_list = (0..ranks.length - 1).to_a
147
+ start_list.sort! { |e1, e2| rank_element(e1) <=> rank_element(e2) }
148
+
149
+ classifications = []
150
+ compute_classifications(classifications, [], @potential_winners, @beat_couples, start_list)
151
+ classifications
152
+ end
153
+
154
+ def compute_classifications(classifications, classif = [], potential_winners, beated_list, start_list)
155
+ if beated_list.empty?
156
+ start_list.permutation.each do |array|
157
+ classifications << classif + array
158
+ end
159
+ else
160
+ if classif.empty? && potential_winners.any?
161
+ potential_winners.each do |element|
162
+ add_element(classifications, classif, nil, beated_list, start_list, element)
163
+ end
164
+ else
165
+ start_list.each do |element|
166
+ add_element(classifications, classif, nil, beated_list, start_list, element)
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+ def add_element(classifications, classif, _potential_winners, beated_list, start_list, element)
173
+ return if beated_list.any? { |c| c[1] == element }
174
+ classification = classif.clone
175
+ classification << element
176
+ next_beated_list = beated_list.clone.delete_if { |c| c[0] == element }
177
+ next_start_list = start_list.clone
178
+ next_start_list.delete(element)
179
+ if next_start_list.empty?
180
+ classifications << classification
181
+ else
182
+ compute_classifications(classifications, classification, nil, next_beated_list, next_start_list)
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,81 @@
1
+ module Vote
2
+ module Condorcet
3
+ module Schulze
4
+ class Input
5
+ def initialize(vote_list, candidate_count = nil)
6
+ @vote_list = vote_list
7
+ @candidate_count = candidate_count
8
+
9
+ if @candidate_count.nil?
10
+ insert_vote_file(@vote_list) if vote_list.is_a?(File)
11
+
12
+ else
13
+ @vote_matrix = ::Matrix.scalar(@candidate_count, 0).extend(Vote::Matrix)
14
+ insert_vote_array(@vote_list) if vote_list.is_a?(Array)
15
+ insert_vote_string(@vote_list) if vote_list.is_a?(String)
16
+ end
17
+ end
18
+
19
+ def insert_vote_array(va)
20
+ va.each do |vote|
21
+ @vote_matrix.each_with_index do |_e, x, y|
22
+ next if x == y
23
+ @vote_matrix[x, y] += 1 if vote[x] > vote[y]
24
+ end
25
+ end
26
+ @vote_count = va.size
27
+ end
28
+
29
+ def insert_vote_string(vs)
30
+ vote_array = []
31
+
32
+ vs.split(/\n|\n\r|\r/).each do |voter|
33
+ voter = voter.split(/=/)
34
+ vcount = (voter.size == 1) ? 1 : voter[0].to_i
35
+
36
+ vcount.times do
37
+ tmp = voter.last.split(/;/)
38
+ tmp2 = []
39
+
40
+ tmp.map! { |e| [e, @candidate_count - tmp.index(e)] }
41
+ # find equal-weighted candidates
42
+ tmp.map do |e|
43
+ if e[0].size > 1
44
+ e[0].split(/,/).each do |f|
45
+ tmp2 << [f, e[1]]
46
+ end # each
47
+ else
48
+ tmp2 << e
49
+ end # if
50
+ end # tmp.map
51
+
52
+ vote_array << (tmp2.sort.map { |e| e = e[1] }) # order, strip & add
53
+ end # vcount.times
54
+ end # vs.split.each
55
+
56
+ insert_vote_array vote_array
57
+ end
58
+
59
+ def insert_vote_file(vf)
60
+ vf.rewind
61
+ @candidate_count = vf.first.strip.to_i # reads first line for count
62
+ @vote_matrix = ::Matrix.scalar(@candidate_count, 0).extend(Vote::Matrix)
63
+ insert_vote_string vf.read # reads rest of file (w/o line 1)
64
+ vf.close
65
+ end
66
+
67
+ def matrix
68
+ @vote_matrix
69
+ end
70
+
71
+ def candidates
72
+ @candidate_count
73
+ end
74
+
75
+ def voters
76
+ @vote_count
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,12 @@
1
+ # encoding: UTF-8
2
+
3
+ # extend matrix with method []=(i,j,v)
4
+ # usage: m = ::Matrix.scalar(size,value).extend(Vote::Matrix)
5
+
6
+ module Vote
7
+ module Matrix
8
+ def []=(i, j, v)
9
+ @rows[i][j] = v
10
+ end
11
+ end
12
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: schulze-vote
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.3
4
+ version: 2.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alessandro Rodi
@@ -23,6 +23,11 @@ files:
23
23
  - README.md
24
24
  - lib/schulze_vote.rb
25
25
  - lib/vote.rb
26
+ - lib/vote/condorcet.rb
27
+ - lib/vote/condorcet/schulze.rb
28
+ - lib/vote/condorcet/schulze/basic.rb
29
+ - lib/vote/condorcet/schulze/input.rb
30
+ - lib/vote/matrix.rb
26
31
  homepage: http://github.com/coorasse/schulze-vote
27
32
  licenses:
28
33
  - MIT