#!/bin/sh # Copyright 2024 Loïc Cerf (lcerf@dcc.ufmg.br) # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or (at # your option) any later version. # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # "Phragmén’s ordered method, formulation 3" na página 22 de # "Phragmén’s and Thiele’s election methods" por Svante Janson (2016). export POSIXLY_CORRECT=1 # para que GNU Awk localize números invalido () { sequencia=$(printf " $sequencia" | tr -sc 0-9 ' ') if [ "$sequencia" != " " ] then if ! printf "$sequencia" | awk -v RS=' ' -v n=$n ' NF && ($0 < 1 || $0 > n || $0 in aprovados) { exit 1 } { aprovados[$0] }' then sequencia=$(printf "$sequencia" | sed -e 's/^ //' -e 's/ $//') printf "Voto \"$sequencia\" inválido: ignorado!\n" >&2 return 0 fi printf "$sequencia\n" >> $TMP/grupos/$(printf "$sequencia" | cut -d ' ' -f 2) fi return 1 } prompt () { printf "Aqui estão os $n candidatos em ordem de desempate:\n\n" nl -w 2 -s '. ' $TMP/candidatos | column -c 80 read -p ' Digite a cédula, uma sequência de números em ordem decrescente de preferência, ou nenhum dígito se não há mais votos: ' sequencia printf '\n' if invalido then printf '\n' prompt fi } if [ -z "$1" -o "$1" = "-h" -o "$1" = "--help" ] then printf "Uso: $0 [-v] candidatos [votos...] Uma candidata ou um candidato por linha não vazia de \"candidatos\". Em caso de empate, quem está antes no arquivo vence. Um voto por linha não vazia de \"votos\": números dos candidatos, em ordem decrescente de preferência.\n" exit fi if [ "$1" = "-v" -o "$1" = "--verbose" ] then shift verbose=1 fi for arquivo in "$@" do if [ ! -r "$arquivo" ] then printf "\"$arquivo\" não pode ser lido!\n" >&2 exit 66 fi done TMP=$(mktemp -dt seq-phragmen_pt.XXXXXX) trap "rm -r $TMP 2>/dev/null" 0 mkdir -p $TMP/grupos $TMP/novos_grupos grep -xv '[[:space:]]*' "$1" > $TMP/candidatos n=$(wc -l < $TMP/candidatos) # Armazenar os votos, já criando os grupos iniciais, um por candidato # posicionado primeiro if [ -n "$2" ] then shift cat "$@" | while read sequencia do invalido done else prompt while [ "$sequencia" != " " ] do prompt done fi if [ -n "$(ls $TMP/grupos)" ] then # Inicializar $TMP/descr com, nesta ordem: # - o identificador de grupo # - o candidato favorito do grupo # - quantos votos no grupo # - a posição do grupo, 0 inicialmente for grupo in $TMP/grupos/* do printf "$(basename $grupo) $(basename $grupo) $(wc -l < $grupo) 0\n" done > $TMP/descr fi while [ -s $TMP/descr ] do # Elegendo um candidato top=$(awk ' { v[$2] += $3 q[$2] += $4 } END { for (candidato in v) print v[candidato] / ++q[candidato] "\t" candidato }' $TMP/descr | sort -k 1,1nr -k 2n | head -1) eleito=$(printf "$top" | cut -f 2) if [ -n "$verbose" ] then awk -v eleito=$eleito ' FILENAME == ARGV[1] { nome[NR] = $0 } FILENAME == ARGV[2] { v[$2] += $3 q[$2] += $4 } END { for (candidato in v) print "* " v[candidato] " / (1 + " q[candidato] ") = " v[candidato] / ++q[candidato] " voto(s) reduzido(s) para " nome[candidato] print "\nCom " v[eleito] / q[eleito] " voto(s) reduzido(s) e possível desempate, seguinte no ranqueamento: " nome[eleito] "\n" }' $TMP/candidatos $TMP/descr else sed -n ${eleito}p $TMP/candidatos fi # A partir dos grupos que acabaram de ser satisfeitos, criar novos # grupos, dependendo das próximas escolhas não eleitas, se tem satisfeitos=$(awk -v eleito=$eleito -v dir=" $TMP/grupos/" '$2 == eleito { printf dir $1 }' $TMP/descr) ranqueamento="$ranqueamento $eleito" awk -v ranqueamento="$ranqueamento " -v dir=$TMP/novos_grupos/ ' { for (i = 1; ranqueamento ~ " " $i " "; ++i) $i = "" } $i { print > dir $i }' $satisfeitos rm $satisfeitos # Remover de $TMP/descr os grupos satisfeitos awk -v eleito=$eleito '$2 != eleito' $TMP/descr > $TMP/tmp mv $TMP/tmp $TMP/descr # Se há novos grupos, colocá-los com os antigos e definir as # posições deles if [ -n "$(ls $TMP/novos_grupos)" ] then w=$(printf "$top" | cut -f 1 | tr , .) for grupo in $TMP/novos_grupos/* do n=$(expr $n + 1) printf "$n $(basename $grupo) " >> $TMP/descr awk -v w=$w 'END { print NR, NR / w }' $grupo >> $TMP/descr if [ -n "$verbose" ] then awk -v w=$w 'END { printf "* %d eleitor(es) satisfeito(s), agora em posição %d / %g = %g prefere(m) ", NR, NR, w, NR / w }' $grupo sed -n $(basename $grupo)p $TMP/candidatos fi mv $grupo $TMP/grupos/$n done fi done