chiark / gitweb /
wip
[appendix-a6.git] / parse
1 #!/usr/bin/perl -w
2 use strict;
3 use utf8;
4 use autodie;
5
6 use Data::Printer;
7 use Graph::Directed;
8
9 binmode STDIN, 'encoding(UTF-8)';
10 binmode STDOUT, 'encoding(UTF-8)';
11 binmode STDERR, 'encoding(UTF-8)';
12
13 our @choices;
14 our %choices;
15 our @invotes;
16 our $defcho;
17 our $quorum = 0;
18
19 sub addchoice {
20     my $choname = shift @_;
21     my $cho = $choices{$choname} = { @_, Index => (scalar @choices) };
22     push @choices, $choname;
23     return $cho;
24 }
25
26 while (<>) {
27     s/\s+$//;
28     next if m/^\s*\#/;
29     next unless m/\S/;
30     next if m/^\s/;
31     if (m/^([A-Z]+)\s*\=\s*(\S.*)$/) {
32         my ($choname, $desc) = ($1,$2);
33         my $cho = addchoice($choname, Desc => $desc);
34         if ($desc =~ m/\[(\d+):(\d+)\]/) {
35             $cho->{Smaj} = [$1,$2];
36         } elsif ($desc =~ m/\[default\]/) {
37             $defcho = $cho;
38         }
39     } elsif (m/^V:\s+(\S+)\s+(\S.*)/) {
40         push @invotes, [ $1, $2 ];
41     } elsif (m/^quorum = ([0-9.]+)$/) {
42         $quorum = $1+0.0;
43     } else {
44         die "invalid input";
45     }
46 }
47
48 $defcho ||= $choices{FD};
49 if (!$defcho) {
50     $defcho = addchoice('FD', Desc => "Further Discussion");
51 }
52 my $defi = $defcho->{Index};
53 die "FD has smaj?!" if $defcho->{Smaj};
54
55 our @vab;
56 # $vab[$ia][$ib] = V(A,B)
57 # Actually, it's a list of voters who prefer A to B (A.6(3)(1))
58
59 # Go through the voters and construct V(A,B)
60
61 print "\nPreference matrix\n";
62
63 foreach my $iv (@invotes) {
64     my ($votestr,$voter) = @$iv;
65     eval {
66         length $votestr eq @choices or die "wrong vote vector length";
67         my @vs = split //, $votestr;
68         foreach my $ix (0..$#vs) {
69             my $vchr = $vs[$ix];
70             if ($vchr eq '-') {
71                 $vs[$ix] = 1000;
72             } elsif ($vchr =~ m/[0-9a-z]/) {
73                 $vs[$ix] = ord($vchr);
74             } else {
75                 die "bad vote char";
76             }
77         }
78         foreach my $ia (0..$#vs) {
79             foreach my $ib ($ia+1..$#vs) {
80                 my $va = $vs[$ia];
81                 my $vb = $vs[$ib];
82                 if ($va < $vb) { push @{ $vab[$ia][$ib] }, $voter }
83                 elsif ($vb < $va) { push @{ $vab[$ib][$ia] }, $voter }
84             }
85         }
86     };
87     die "voter $voter $@" if $@;
88 }
89
90 our @ch = map { $choices{$_} } @choices;
91
92 # Print the counts V(A,B)
93 foreach my $iy (-2..$#ch) {
94     foreach my $ix (-2..$#ch) {
95         if ($iy==-1) {
96             if ($ix==-1) {
97                 printf "+";
98             } else {
99                 printf "------";
100             }
101         } elsif ($ix==-1) {
102             printf "|";
103         } elsif ($ix==-2 && $iy==-2) {
104             printf "V(Y,X)";
105         } elsif ($iy==-2) {
106             printf "%5s ", $choices[$ix];
107         } elsif ($ix==-2) {
108             printf "%5s ", $choices[$iy];
109         } else {
110             my $v = \( $vab[$iy][$ix] );
111             $$v ||= [ ];
112             if (@$$v) {
113                 printf "%5d ", (scalar @$$v);
114             } else {
115                 printf "%5s ", "";
116             }
117         }
118     }
119     printf "\n";
120 }
121
122 sub drop ($$) {
123     my ($i,$why) = @_;
124     print "dropping $choices[$i]: $why\n";
125     $ch[$i]{Dropped} = $why;
126 }
127
128 print "\nQuorum A.6(2) (quorum is $quorum)\n";
129
130 foreach my $i (0..$#choices) {
131     next if $ch[$i]{Dropped};
132     next if $i == $defi;
133     my $v = $vab[$i][$defi];
134     next if $v >= $quorum;
135     drop $i, "quorum ($v < $quorum)";
136 }
137
138 print "\nMajority ratio A.6(3)\n";
139
140 foreach my $i (0..$#choices) {
141     next if $ch[$i]{Dropped};
142     next if $i == $defi;
143     my $majr = $ch[$i]{Smaj};
144     $majr ||= [1,1]; # A.6(3)(3)
145     my $vad = scalar @{ $vab[$i][$defi] };
146     my $vda = scalar @{ $vab[$defi][$i] };
147     next if $vad * $majr->[1] > $vda * $majr->[0];
148     drop $i, "majority ratio ($vad * $majr->[1] <= $vda * $majr->[0])";
149 }
150
151 print "\nDefeats A.6(4)\n";
152
153 my $defeats = Graph::Directed->new;
154
155 sub chremain () {
156     return grep { !$ch[$_]{Dropped} } (0..$#ch);
157 }
158
159 foreach my $ia (chremain()) {
160     $defeats->add_vertex($choices[$ia]);
161     foreach my $ib (chremain()) {
162         my $vab = scalar @{ $vab[$ia][$ib] };
163         my $vba = scalar @{ $vab[$ib][$ia] };
164         next unless $vab > $vba;
165         my $diff = $vab - $vba;
166         print "defeat: $choices[$ia] beats $choices[$ib]",
167             " ($vab > $vba: $diff)\n";
168         $defeats->add_edge($choices[$ia],$choices[$ib]);
169     }
170 }
171
172 sub chvab ($$) {
173     my ($ca,$cb) = @_;
174     my $v = $vab[ $choices{$ca}{Index} ][ $choices{$cb}{Index} ];
175     return scalar @$v;
176 }
177
178 sub weaker ($$) {
179     # A.6(7)(1)
180     my ($def1,$def2) = @_;
181     my ($ca,$cx) = @$def1;
182     my ($cb,$cy) = @$def1;
183     return 1 if chvab($ca, $cx) < chvab($cb, $cy);
184     return 1 if chvab($ca, $cx) == chvab($cb, $cy)
185         && chvab($cx, $ca) > chvab($cy, $cb);
186     return 0;
187 }
188
189 our $schwartz;
190
191 for (;;) {
192     # loop from A6(5)
193
194     print "defeats graph: $defeats\n";
195
196     print "\nTransitive closure A.6(5)\n";
197
198     my $tdefeats = $defeats->transitive_closure();
199
200     print "closure graph: $tdefeats\n";
201
202     print "\nSchwartz set A.6(6)\n";
203     
204     $schwartz = $defeats->copy();
205
206     foreach my $ia (chremain()) {
207         foreach my $ib (chremain()) {
208             next if $tdefeats->has_edge($choices[$ia],$choices[$ib]);
209             next if !$tdefeats->has_edge($choices[$ib],$choices[$ia]);
210             print "not in Schwartz set: $choices[$ia] because $choices[$ib]\n";
211             $schwartz->delete_vertex($choices[$ia]);
212             last;
213         }
214     }
215
216     print "\nDropping weakest defeats A.6(7)\n";
217
218     our @weakest = ();
219
220     foreach my $edge ($schwartz->edges()) {
221 #       print " considering @$edge\n";
222         if (!@weakest) {
223             # no weakest edges yet
224         } elsif (weaker($edge, $weakest[0])) {
225             # this edge is weaker than previous weakest, start new set
226             @weakest = ();
227         } elsif (weaker($weakest[0], $edge)) {
228             # weakest edge is weaker than this one, ignore this one
229             next;
230         } else {
231             # weakest edge is exactly as weak as this one, add this one
232         }
233         push @weakest, $edge;
234     }
235
236     last unless @weakest;
237
238     my $w = $weakest[0];
239     printf "weakest defeats %d > %d\n", 
240         chvab($w->[0], $w->[1]),
241         chvab($w->[1], $w->[0]);
242     foreach my $weakest (@weakest) {
243         my ($ca,$cb) = @$weakest;
244         print "weakest defeat $ca > $cb\n";
245         $defeats->delete_edge($ca,$cb);
246     }
247
248     print "defeats within the Schwartz set, round again\n";
249 }
250
251 print "no defeats within the Schwartz set\n";
252 print "final schwartz set:\n\n";
253
254 if ($schwartz->vertices() == 1) {
255     print "WINNER IS:\n";
256 } else {
257     print "WINNER IS ONE OF (CASTING VOTE DECIDES):\n";
258 }
259
260 printf "    %-5s %s\n", $_, $choices{$_}{Desc}
261     foreach ($schwartz->vertices());
262
263 print ".\n";
264
265 #p %choices;
266 #p @vab;