Skip to content

Instantly share code, notes, and snippets.

@rbeer
Last active October 18, 2017 13:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rbeer/f0fa8a61c53aaa366ec803c7d0f1bb49 to your computer and use it in GitHub Desktop.
Save rbeer/f0fa8a61c53aaa366ec803c7d0f1bb49 to your computer and use it in GitHub Desktop.
Saint Lague / Schepers committee seat distribution

Webster/Sainte-Laguë/Schepers method

https://en.m.wikipedia.org/wiki/Webster/Sainte-Laguë_method

Calculates seat distribution of a committee, using the Webster/Sainte-Laguë/Schepers method for seat allocation.

It takes a number of seats in the committee and a set of parties with their number of seats in the actual parliament.

const committeeSize = 34;

const parties = {
  cdu: {
    seats: 123
  },
  spd: {
    seats: 120
  },
  ...
};
// parliament size is calculated by adding the parties' seats - duh :P

It returns an Object with the initial object's keys (preferably the parties' names) and the seats for that key in the committee.

=> {
  cdu: 17,
  spd: 11,
  ...
}
#!/usr/bin/env node
const _ = require('lodash');
const COMMITTEE_SIZE = 34;
const PARTIES = {
cdu: {
seats: 311
},
spd: {
seats: 193
},
left: {
seats: 64
},
greens: {
seats: 63
}
};
Object.defineProperty(PARTIES, 'seatsTotal', {
get: () => {
let seatsTotal = 0;
for(let k in PARTIES) {
seatsTotal += PARTIES[k].seats || 0;
}
return seatsTotal;
}
});
((...args) => {
let [committeeSize, parties] = args;
/**
* Generates Saint Lague/Schepers distribution
* @return {[type]} [description]
*/
const getSLS = (committeeSize, parties) => {
console.log('Generating SLS distribution for a');
console.log('committee with', committeeSize, 'seats.');
console.log('Eligable parties: ', _.keys(parties));
console.log('Calculting ranks...');
// add ranks to parties
let rankedParties = _.mapValues(parties, (party) => {
party.ranks = calculateRanks(committeeSize, parties.seatsTotal, party);
return party;
});
let ranks = _.reduce(rankedParties, (ranks, party, key) => {
ranks[key] = party.ranks;
return ranks;
}, {});
console.log('Comparing ranks...');
let partyKeys = _.keys(ranks);
let committeeSeats = _.zipObject(partyKeys, new Array(partyKeys.length).fill(0));
for (let i = 0; i < committeeSize; i++) {
let smallest = determineSmallestRank(ranks);
ranks = smallest.ranks;
committeeSeats[smallest.key]++;
}
console.log('Done.');
console.log('Your comiittee\'s seat distribution is', committeeSeats);
};
const determineSmallestRank = (ranks) => {
// find smallest and its key
let smallest = Infinity;
let smallestKey = '';
for (let key in ranks) {
let probe = ranks[key][0];
if (probe < smallest) {
smallest = probe;
smallestKey = key;
}
}
if (smallest < Infinity) {
// remove smallest from array under key
ranks[smallestKey].shift();
}
return { ranks: ranks, key: smallestKey };
};
const calculateRanks = (committeeSize, seatsTotal, party) => {
let incrementalFactor = 0.5;
let seatsFactor = seatsTotal / party.seats;
// - number of ranks === total seats in committee, to be safe
// (although that scenario would mean a new dictator ^_^)
// - first rank wants 0.5, so we 'increment after'
// - example uses 3 decimals
// round(rank * 1000) / 1000
// round(12.1839384 * 1000 [=12183.9384]) [= 12184] / 1000 = 12.184
return new Array(committeeSize).fill(0)
.map(() => Math.round((seatsFactor * incrementalFactor++) * 1000) / 1000);
};
getSLS(committeeSize, parties);
})(COMMITTEE_SIZE, PARTIES);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment