vote-schulze 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,13 +1,7 @@
1
1
  source "http://rubygems.org"
2
- # Add dependencies required to use your gem here.
3
- # Example:
4
- # gem "activesupport", ">= 2.3.5"
5
-
6
- # Add dependencies to develop your gem here.
7
- # Include everything needed to run rake, tests, features, etc.
8
2
  group :development do
9
- gem "rspec", "~> 2.3.0"
10
3
  gem "bundler", "~> 1.0.0"
11
4
  gem "jeweler", "~> 1.6.0"
12
- gem "rcov", ">= 0"
5
+ gem "rspec", "~> 2.3.0"
13
6
  end
7
+
@@ -8,7 +8,6 @@ GEM
8
8
  git (>= 1.2.5)
9
9
  rake
10
10
  rake (0.8.7)
11
- rcov (0.9.9)
12
11
  rspec (2.3.0)
13
12
  rspec-core (~> 2.3.0)
14
13
  rspec-expectations (~> 2.3.0)
@@ -24,5 +23,4 @@ PLATFORMS
24
23
  DEPENDENCIES
25
24
  bundler (~> 1.0.0)
26
25
  jeweler (~> 1.6.0)
27
- rcov
28
26
  rspec (~> 2.3.0)
data/README.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  This gem is a Ruby implementation of the Schulze voting method (with help of the Floyd–Warshall algorithm), a type of the Condorcet voting methods.
4
4
 
5
+ **Very alpha! Only basic functionality. No tests yet!**
6
+
7
+ (If you want to write the tests: fork, write, test, push request - Thanks!)
8
+
5
9
  Wikipedia:
6
10
 
7
11
  * [Schulze method](http://en.wikipedia.org/wiki/Schulze_method) ([deutsch](http://de.wikipedia.org/wiki/Schulze-Methode))
@@ -17,18 +21,97 @@ gem install vote-schulze
17
21
 
18
22
  ``` ruby
19
23
  require 'vote-schulze'
20
- vs = SchulzeBasic.do candidate_count, vote_list
21
- vs.ranking_array
24
+ vs = SchulzeBasic.do vote_list, candidate_count
25
+ vs.ranks
26
+ vs.ranks_abc
22
27
  ```
23
28
 
24
29
  `SchulzeBasic.do` - SchulzeBasic is a short term for `Vote::Condorcet::Schulze::Basic` and `.do` is a method of this class!
25
30
 
26
31
  Input:
27
32
 
33
+ * `vote_list`
34
+ * Array of Arrays: votes of each voter as weights `[ [A,B,C,...],[A,B,C,...],[A,B,C,...] ]`
35
+ * String: "A;B;C\nA;B;C\n;3=A;B;C..."
36
+ * File: first line **must** be a single integer, following lines like vote_list type String (see vote lists under `examples` directory)
28
37
  * `candidate_count` Integer: number of candidates
29
- * `vote_list` Array of Arrays: votes of each voter as weights `[ [A,B,C,...],[A,B,C,...],[A,B,C,...] ]`
38
+ * **required** for vote_list types of Array and String
39
+ * _leave empty if vote_list is a File handle!_
40
+
41
+ ### String/File format:
42
+
43
+ A typical voters line looks like this:
44
+
45
+ ```
46
+ A;B;C;D;E;F
47
+ ```
48
+
49
+ You also can say that _n_ voters have the same preferences:
50
+
51
+ ```
52
+ n=F;E;D;C;B;A
53
+ ```
54
+
55
+ where _n_ is an integer value for the count.
56
+
57
+ Also it's possible to say that a voter has candidates equally weighted:
58
+
59
+ ```
60
+ A,B;C,D;E,F
61
+ ```
62
+
63
+ which means, that A + B, C + D and E + F are on the same weight level.
64
+
65
+ Here only 3 weight levels are used: (A,B) = 3, (C,D) = 2, (E,F) = 1
66
+
67
+ ### Why I must write the candidate count in the first line of the vote file?
68
+
69
+ _or: Why I must give a candidate count value for Array/String inputs?_
70
+
71
+ Very easy: The reason is, that voters can leave out candidates (they give no special preferences).
72
+
73
+ So, vote-schulze needs to know, how many real candidates are in the voting process.
74
+
75
+ Okay, for Array inputs it's currently a little bit overhead, because the voters array normally should have the size of the candidates count.
76
+ See it as an visual reminder while coding with this gem.
77
+
78
+ ### Examples
30
79
 
31
- preference order to weight example:
80
+ #### Array
81
+
82
+ (Only weight values, no letters here! See section "_preference order to weight_ example")
83
+
84
+ ``` ruby
85
+ require 'vote-schulze'
86
+ vote_list_array = [[3,2,1],[1,3,2],[3,1,2]]
87
+ vs = SchulzeBasic.do vote_list_array, 3
88
+ vs.ranks_abc #=> result
89
+ ```
90
+
91
+ #### String
92
+
93
+ ``` ruby
94
+ require 'vote-schulze'
95
+ vote_list_string = <<EOF
96
+ A;B;C
97
+ B;C;A
98
+ A;C;B
99
+ A,C,B
100
+ 4=C;A;B
101
+ EOF
102
+ vs = SchulzeBasic.do vote_list_string, 3
103
+ vs.ranks_abc #=> result
104
+ ```
105
+
106
+ #### File
107
+
108
+ ``` ruby
109
+ require 'vote-schulze'
110
+ vs = SchulzeBasic.do File.open('path/to/vote.list')
111
+ vs.ranks_abc #=> result
112
+ ```
113
+
114
+ ### _preference order to weight_ example
32
115
 
33
116
  ```
34
117
  voter => A D C B
@@ -56,6 +139,23 @@ Output:
56
139
 
57
140
  * `.ranking_array` Array: numbers of total wins for each candidate `[candidate A, candidate B, candidate C, ...]`
58
141
 
142
+ ## Example
143
+
144
+ Reference calculation: [Schulze Methode | blog.cgiesel.de (german)](http://blog.cgiesel.de/schulze-methode/)
145
+
146
+ Example file under `examples/vote4.list`
147
+
148
+ Result should be:
149
+ ``` ruby
150
+ sb = SchulzeBasic.do File.open('../examples/vote4.list')
151
+ sb.rank_abc
152
+ #=> ["C:1", "D:2", "B:3", "A:4"]
153
+ ```
154
+
155
+ which is the same result of the reference above.
156
+
157
+ The result strings are always in format `Candidate:Position`, because it's possible that multiple candidates can be on the same rank.
158
+
59
159
  ## Contributing to vote-schulze
60
160
 
61
161
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
data/Rakefile CHANGED
@@ -18,7 +18,7 @@ Jeweler::Tasks.new do |gem|
18
18
  gem.homepage = "http://github.com/asaaki/vote-schulze"
19
19
  gem.license = "MIT"
20
20
  gem.summary = %Q{Schulze method implementation in Ruby (Condorcet voting method)}
21
- gem.description = %Q{This gem is a Ruby implementation of the Schulze voting method with help of the Floyd–Warshall algorithm, a type of the Condorcet voting methods.}
21
+ gem.description = %Q{This gem is a Ruby implementation of the Schulze voting method (with help of the Floyd–Warshall algorithm), a type of the Condorcet voting methods.}
22
22
  gem.email = "chris@dinarrr.com"
23
23
  gem.authors = ["Christoph Grabo"]
24
24
  # dependencies defined in Gemfile
@@ -31,11 +31,6 @@ RSpec::Core::RakeTask.new(:spec) do |spec|
31
31
  spec.pattern = FileList['spec/**/*_spec.rb']
32
32
  end
33
33
 
34
- RSpec::Core::RakeTask.new(:rcov) do |spec|
35
- spec.pattern = 'spec/**/*_spec.rb'
36
- spec.rcov = true
37
- end
38
-
39
34
  task :default => :spec
40
35
 
41
36
  require 'rake/rdoctask'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
@@ -0,0 +1,7 @@
1
+ 4
2
+ 3=a;c;d;b
3
+ 9=b;a;c;d
4
+ 8=c;d;a;b
5
+ 5=d;a;b;c
6
+ 5=d;b;c;a
7
+
@@ -0,0 +1,44 @@
1
+ 6
2
+ 2=A;B;C;D;E;F
3
+ 3=A,B;C,D;E,F
4
+ F;C;D;A;B;E
5
+ A;D;C;F;E;B
6
+ F;E;D;C;B;A
7
+ D;E;A,B;C;F
8
+ B;A;C;D;E;F
9
+ 9=C;A;F,E;B;D
10
+ D;A;E;F;C;B
11
+ E;B;F;D;C;A
12
+ F;C;D;A,E,B
13
+ 6=A;D;E;B;F;C
14
+ B;E;C;F;A;D
15
+ C;F;A,B;D;E
16
+ D;A;B;E;C;F
17
+ E;B,F;C;D;A
18
+ F;C;A;D;E;B
19
+ A;D;B;E;F;C
20
+ B,E;F;A;C;D
21
+ 2=C;F;D;B;A;E
22
+ D;A,E,B;C;F
23
+ E;B;C;F;D;A
24
+ F;C;D;A;E;B
25
+ E;D;C,F,B;A
26
+ D;E;B;F;A;C
27
+ 3=C;F;A;E;B;D
28
+ B;A;D;C,F;E
29
+ A;B;D;E;C;F
30
+ A;C;F;B;D,E
31
+ A;D;E;F;C;B
32
+ B;E;D;C;F;A
33
+ B;F;C,E,A,D
34
+ 2=C;A;B;D;E;F
35
+ D;B,F,E;C;A
36
+ D;C;A;F;E;B
37
+ E,D;F;A;B;C
38
+ E;F;A;B;C;D
39
+ F;E;B;A;C;D
40
+ F;A,D,C;B;E
41
+ A;B;C;E;D;F
42
+ B;C;F;A;D;E
43
+ 2=C;D;E;B,F;A
44
+
@@ -6,40 +6,32 @@ module Vote
6
6
 
7
7
  class Basic
8
8
 
9
- def initialize
10
- #self
11
- end
12
-
13
- def load candidate_count, vote_matrix
14
-
15
- @candidate_count = candidate_count
16
-
17
- @vote_matrix = vote_matrix.is_a?(Vote::Condorcet::Schulze::Input) ? \
18
- vote_matrix.matrix : \
9
+ def load vote_matrix, candidate_count=nil
10
+ input = vote_matrix.is_a?(Vote::Condorcet::Schulze::Input) ? \
11
+ vote_matrix : \
19
12
  Vote::Condorcet::Schulze::Input.new(
20
- candidate_count,
21
- vote_matrix
22
- ).matrix
23
-
13
+ vote_matrix,
14
+ candidate_count
15
+ )
16
+ @vote_matrix = input.matrix
17
+ @candidate_count = input.candidates
18
+ @vote_count = input.voters
24
19
  self
25
20
  end
26
21
 
27
22
  private
28
23
 
29
24
  def play
30
-
31
25
  @play_matrix = ::Matrix.scalar(@candidate_count,0).extend(Vote::Matrix)
32
-
33
26
  # step 1: find matches with wins
34
27
  @candidate_count.times do |i|
35
28
  @candidate_count.times do |j|
36
29
  next if i==j
37
30
  if @vote_matrix[i,j] > @vote_matrix[j,i]
38
31
  @play_matrix[i,j] = @vote_matrix[i,j]
39
- end
40
- end
41
- end
42
-
32
+ end #loop3
33
+ end #loop2
34
+ end #loop1
43
35
  #step 2: find strongest pathes
44
36
  @candidate_count.times do |i|
45
37
  @candidate_count.times do |j|
@@ -51,38 +43,34 @@ module Vote
51
43
  @play_matrix[j,k],
52
44
  [ @play_matrix[j,i], @play_matrix[i,k] ].min
53
45
  ].max
54
- end
55
- end
56
- end
57
-
58
- self
59
- end
46
+ end #loop3
47
+ end #loop2
48
+ end #loop1
49
+ end #def
60
50
 
61
51
 
62
52
  def result
63
-
64
53
  @result_matrix = ::Matrix.scalar(@candidate_count,0).extend(Vote::Matrix)
65
-
66
54
  @result_matrix.each_with_index do |e,x,y|
67
55
  next if x==y
68
56
  @result_matrix[x,y] = e+1 if @play_matrix[x,y] > @play_matrix[y,x]
69
57
  end
70
-
71
- self
72
58
  end
73
59
 
74
-
75
60
  def rank
61
+ @ranking = @result_matrix.
62
+ row_vectors.map { |e| e.inject(0) { |s,v| s += v } }
63
+ end
76
64
 
77
- @ranking = Array.new(@candidate_count,0)
78
-
79
- @result_matrix.row_vectors.each_with_index do |v,i|
80
- v.to_a.map do |e|
81
- @ranking[i] += e
82
- end
83
- end
84
-
85
- self
65
+ def rank_abc
66
+ r = @ranking
67
+ rmax = r.max
68
+ abc = r.map{|e|
69
+ [e,(r.index(e)+65).chr] # => [int,letter]
70
+ }.
71
+ sort.reverse. # bring in correct order
72
+ map{|e| "#{e[1]}:#{rmax-e[0]+1}" } # => "letter:int"
73
+ @ranking_abc = abc
86
74
  end
87
75
 
88
76
  public
@@ -91,6 +79,7 @@ module Vote
91
79
  play
92
80
  result
93
81
  rank
82
+ rank_abc
94
83
  end
95
84
 
96
85
  def vote_matrix
@@ -102,15 +91,20 @@ module Vote
102
91
  def result_matrix
103
92
  @result_matrix
104
93
  end
105
- def ranking_array
94
+ def ranks
106
95
  @ranking
107
96
  end
108
-
97
+ def ranks_abc
98
+ @ranking_abc
99
+ end
100
+ def voters
101
+ @vote_count
102
+ end
109
103
 
110
104
  # All-in-One class method to get a calculated SchulzeBasic object
111
- def self.do candidate_count, vote_matrix
105
+ def self.do vote_matrix, candidate_count=nil
112
106
  instance = new
113
- instance.load candidate_count, vote_matrix
107
+ instance.load vote_matrix, candidate_count
114
108
  instance.run
115
109
  instance
116
110
  end
@@ -6,12 +6,19 @@ module Vote
6
6
 
7
7
  class Input
8
8
 
9
- @vote_matrix = nil
9
+ def initialize vote_list, candidate_count = nil
10
+ @vote_list = vote_list
11
+ @candidate_count = candidate_count
12
+
13
+ if @candidate_count.nil?
14
+ insert_vote_file(@vote_list) if vote_list.is_a?(File)
15
+
16
+ else
17
+ @vote_matrix = ::Matrix.scalar(@candidate_count,0).extend(Vote::Matrix)
18
+ insert_vote_array(@vote_list) if vote_list.is_a?(Array)
19
+ insert_vote_string(@vote_list) if vote_list.is_a?(String)
20
+ end
10
21
 
11
- def initialize candidate_count, vote_list
12
- @vote_matrix = ::Matrix.scalar(candidate_count,0).extend(Vote::Matrix)
13
- insert_vote_array(vote_list) if vote_list.is_a?(Array)
14
- insert_vote_file(vote_list) if vote_list.is_a?(File)
15
22
  end
16
23
 
17
24
  def insert_vote_array va
@@ -21,20 +28,59 @@ module Vote
21
28
  @vote_matrix[x,y] += 1 if vote[x]>vote[y]
22
29
  end
23
30
  end
31
+ @vote_count = va.size
24
32
  end
25
33
 
26
- def insert_vote_list vl
27
- #...
34
+ def insert_vote_string vs
35
+ vote_array = []
36
+
37
+ vs.split(/\n|\n\r|\r/).each do |voter|
38
+ voter = voter.split(/=/)
39
+ vcount = (voter.size==1) ? 1 : voter[0].to_i
40
+
41
+ vcount.times do
42
+ tmp = voter.last.split(/;/)
43
+ tmp2 = []
44
+
45
+ tmp.map!{|e| [e,@candidate_count-tmp.index(e)] }
46
+ # find equal-weighted candidates
47
+ tmp.map do |e|
48
+ if e[0].size > 1
49
+ e[0].split(/,/).each do |f|
50
+ tmp2 << [f,e[1]]
51
+ end #each
52
+ else
53
+ tmp2 << e
54
+ end #if
55
+ end #tmp.map
56
+
57
+ vote_array << (tmp2.sort.map{|e| e = e[1] }) # order, strip & add
58
+ end #vcount.times
59
+ end #vs.split.each
60
+
61
+ insert_vote_array vote_array
28
62
  end
29
63
 
30
64
  def insert_vote_file vf
31
- #...
65
+ vf.rewind
66
+ @candidate_count = vf.first.strip.to_i #reads first line for count
67
+ @vote_matrix = ::Matrix.scalar(@candidate_count,0).extend(Vote::Matrix)
68
+ insert_vote_string vf.read # reads rest of file (w/o line 1)
69
+ vf.close
32
70
  end
33
71
 
34
72
  def matrix
35
73
  @vote_matrix
36
74
  end
37
75
 
76
+ def candidates
77
+ @candidate_count
78
+ end
79
+
80
+ def voters
81
+ @vote_count
82
+ end
83
+
38
84
  end
39
85
 
40
86
  end
@@ -5,12 +5,12 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{vote-schulze}
8
- s.version = "0.1.0"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Christoph Grabo"]
12
- s.date = %q{2011-05-19}
13
- s.description = %q{This gem is a Ruby implementation of the Schulze voting method with help of the Floyd–Warshall algorithm, a type of the Condorcet voting methods.}
12
+ s.date = %q{2011-05-22}
13
+ s.description = %q{This gem is a Ruby implementation of the Schulze voting method (with help of the Floyd–Warshall algorithm), a type of the Condorcet voting methods.}
14
14
  s.email = %q{chris@dinarrr.com}
15
15
  s.extra_rdoc_files = [
16
16
  "LICENSE.txt",
@@ -26,6 +26,8 @@ Gem::Specification.new do |s|
26
26
  "README.md",
27
27
  "Rakefile",
28
28
  "VERSION",
29
+ "examples/vote4.list",
30
+ "examples/vote6.list",
29
31
  "lib/vote-schulze.rb",
30
32
  "lib/vote.rb",
31
33
  "lib/vote/condorcet.rb",
@@ -48,21 +50,18 @@ Gem::Specification.new do |s|
48
50
  s.specification_version = 3
49
51
 
50
52
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
51
- s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
52
53
  s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
53
54
  s.add_development_dependency(%q<jeweler>, ["~> 1.6.0"])
54
- s.add_development_dependency(%q<rcov>, [">= 0"])
55
+ s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
55
56
  else
56
- s.add_dependency(%q<rspec>, ["~> 2.3.0"])
57
57
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
58
58
  s.add_dependency(%q<jeweler>, ["~> 1.6.0"])
59
- s.add_dependency(%q<rcov>, [">= 0"])
59
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
60
60
  end
61
61
  else
62
- s.add_dependency(%q<rspec>, ["~> 2.3.0"])
63
62
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
64
63
  s.add_dependency(%q<jeweler>, ["~> 1.6.0"])
65
- s.add_dependency(%q<rcov>, [">= 0"])
64
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
66
65
  end
67
66
  end
68
67
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vote-schulze
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,23 +9,12 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-05-19 00:00:00.000000000 +02:00
12
+ date: 2011-05-22 00:00:00.000000000 +02:00
13
13
  default_executable:
14
14
  dependencies:
15
- - !ruby/object:Gem::Dependency
16
- name: rspec
17
- requirement: &26515360 !ruby/object:Gem::Requirement
18
- none: false
19
- requirements:
20
- - - ~>
21
- - !ruby/object:Gem::Version
22
- version: 2.3.0
23
- type: :development
24
- prerelease: false
25
- version_requirements: *26515360
26
15
  - !ruby/object:Gem::Dependency
27
16
  name: bundler
28
- requirement: &26514880 !ruby/object:Gem::Requirement
17
+ requirement: &28284620 !ruby/object:Gem::Requirement
29
18
  none: false
30
19
  requirements:
31
20
  - - ~>
@@ -33,10 +22,10 @@ dependencies:
33
22
  version: 1.0.0
34
23
  type: :development
35
24
  prerelease: false
36
- version_requirements: *26514880
25
+ version_requirements: *28284620
37
26
  - !ruby/object:Gem::Dependency
38
27
  name: jeweler
39
- requirement: &26514400 !ruby/object:Gem::Requirement
28
+ requirement: &28284140 !ruby/object:Gem::Requirement
40
29
  none: false
41
30
  requirements:
42
31
  - - ~>
@@ -44,20 +33,20 @@ dependencies:
44
33
  version: 1.6.0
45
34
  type: :development
46
35
  prerelease: false
47
- version_requirements: *26514400
36
+ version_requirements: *28284140
48
37
  - !ruby/object:Gem::Dependency
49
- name: rcov
50
- requirement: &26513920 !ruby/object:Gem::Requirement
38
+ name: rspec
39
+ requirement: &28283660 !ruby/object:Gem::Requirement
51
40
  none: false
52
41
  requirements:
53
- - - ! '>='
42
+ - - ~>
54
43
  - !ruby/object:Gem::Version
55
- version: '0'
44
+ version: 2.3.0
56
45
  type: :development
57
46
  prerelease: false
58
- version_requirements: *26513920
59
- description: This gem is a Ruby implementation of the Schulze voting method with help
60
- of the Floyd–Warshall algorithm, a type of the Condorcet voting methods.
47
+ version_requirements: *28283660
48
+ description: This gem is a Ruby implementation of the Schulze voting method (with
49
+ help of the Floyd–Warshall algorithm), a type of the Condorcet voting methods.
61
50
  email: chris@dinarrr.com
62
51
  executables: []
63
52
  extensions: []
@@ -74,6 +63,8 @@ files:
74
63
  - README.md
75
64
  - Rakefile
76
65
  - VERSION
66
+ - examples/vote4.list
67
+ - examples/vote6.list
77
68
  - lib/vote-schulze.rb
78
69
  - lib/vote.rb
79
70
  - lib/vote/condorcet.rb
@@ -101,7 +92,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
101
92
  version: '0'
102
93
  segments:
103
94
  - 0
104
- hash: 2256632431419553553
95
+ hash: -2878999466654239120
105
96
  required_rubygems_version: !ruby/object:Gem::Requirement
106
97
  none: false
107
98
  requirements: