schulze-vote 2.0.3 → 2.0.4

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 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