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