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