Bladeren bron

this is now a thing

master
alexkraak 4 jaren geleden
commit
d4b1c4c9c9
1 gewijzigde bestanden met toevoegingen van 170 en 0 verwijderingen
  1. +170
    -0
      index.js

+ 170
- 0
index.js Bestand weergeven

@@ -0,0 +1,170 @@
exports = module.exports = {};
/*
Assuming stv vote objects are an array of these(in order of preference):
[bob, tom, tim, jim]

To calculate stv im doing the following:
calculateQuota
while some seats don't have winners
count votes by first preference
if a candidate has at least the quota:
elect them
distribute their excess votes
otherwise:
eliminate candidate with least votes
redistribute their votes
*/

// Caclulates Droop quota
// https://en.wikipedia.org/wiki/Droop_quota
let calculateQuota = function(nballots, seats) {
return (Math.floor(nballots / (seats + 1)) + 1);
};

// STV as found on wikipedia
exports.stvCount = function(candidates, ballots, seatsToFill) {
const votesToWin = calculateQuota(ballots.length, seatsToFill);
let winners = [];
let votes = initialVotesPerCandidate(ballots);
while (winners.length < seatsToFill) {
let wins = calculateWinners();
if (wins.length > 0) {
winners = winners.concat(wins);
// eslint-disable-next-line no-loop-func
wins.forEach(c => {
votes = spreadWinnerExcess(c, ballots, votesToWin, votes);
delete votes[c];
ballots = removeAllCandidate(c, ballots);
wins = [];
});
} else {
let losers = calculateLosers();
if (losers.length !== 0) {
// eslint-disable-next-line no-loop-func
losers.forEach(c => {
votes = spreadLoserExcess(c, ballots, votes);
delete votes[c];
ballots = removeAllCandidate(c, ballots);
});
} else {
let highestC = '';
let highestN = 0;
for (let p in votes) {
if (votes[p] > highestN) {
highestN = votes[p];
highestC = p;
}
}
if (!winners.includes(highestC)) {
winners.push(highestC);
}
}
}
}

// Returns array of candidates over quota
function calculateWinners() {
let won = [];
for (let i in votes) {
if (votes[i] >= votesToWin) {
won.push(i);
}
}
return won;
}

// Returns array of candidates who did worst in a round
function calculateLosers() {
let loss = [];
let min = Math.min(...Object.values(votes));
for (let i in votes) {
if (votes[i] === min) {
loss.push(i);
}
}
return loss;
}

// Creates an object of each candidate and the number
// of first choice votes they got
function initialVotesPerCandidate(bs) {
let votesPerCandidate = {};
let firsts = bs.map(ballot => ballot[0]);
firsts.forEach(vote => {
if (votesPerCandidate.hasOwnProperty(vote)) {
votesPerCandidate[vote] += 1;
} else {
votesPerCandidate[vote] = 1;
}
});
return votesPerCandidate;
}

// Removes all of a candidate from the ballots
function removeAllCandidate(candidate, votestofilter) {
return votestofilter.map(b => b.filter(c => {
return c !== candidate;
})).filter(w => w.length > 0);
}

// If a candidate has lost, proportionally spreads
// their votes to their second choices
function spreadLoserExcess(candidate, ballots, votes) {
const ballotsForThisCandidate = ballots.filter(ballot => {
return ballot[0] === candidate;
});
let secondPref = {};
let x = ballotsForThisCandidate
.map(a => a[1]);
x.forEach(v => {
if (secondPref.hasOwnProperty(v)) {
secondPref[v] += 1;
} else {
secondPref[v] = 1;
}
});
let cTotal = votes[candidate];
let total = Object.values(secondPref)
.reduce((a, b) => a + b, 0);
const calcVoteIncrease = (spTotal, candidateTotal, secondPref) => {
return (secondPref / spTotal) * (candidateTotal);
};
secondPref.forEach(c => {
votes[c] += calcVoteIncrease(total, cTotal, secondPref[c]);
});
return votes;
}
// Spreads excess votes from first choice winners
// to their second choices
function spreadWinnerExcess(candidate, ballots, quota, votes) {
const ballotsForThisCandidate = ballots.filter(ballot => {
return ballot[0] === candidate;
});
let secondPref = {};
ballotsForThisCandidate
.map(a => a[1]).forEach(v => {
if (secondPref.hasOwnProperty(v)) {
secondPref[v] += 1;
} else {
secondPref[v] = 1;
}
});
const excess = votes[candidate] - quota;

const totalVotesForWinner = votes[candidate];
const transferAmount = (upcandidate, excess, totalVotesWinner) => {
let t = (secondPref[upcandidate] / totalVotesWinner) * excess;
votes[upcandidate] += t;
};

secondPref.forEach(c => {
transferAmount(c, excess, totalVotesForWinner);
});
return votes;
}

return winners;

};

Laden…
Annuleren
Opslaan