Commit 50001c8a 50001c8a924e40b23994164b25b2d60b57c38d3c by Vincent Peybernes

#4 Implement Relative Placement

1 parent 287e472b
Pipeline #5 for 50001c8a passed in 33 seconds
/**
* Created by Techniv on 30/11/2016.
* @module relative_placement
*/
(function (define) {
// TODO define exhaustive module platform
module.exports = define();
})(function(){
/**
*
* @name RelativePlacement
* @constructor
* @property {Candidate[]} candidateList
* @property {String[]} votes
* @property {String[][]} votes
* @property {Number} numberOfCandidates
*
*/
......@@ -29,6 +29,7 @@
* @this RelativePlacement
*/
RelativePlacement.prototype.addCandidate = function addCandidate(candidateName){
if(this.votes.length) throw new Error("Adding candidates after votes is not permit.");
if(this.candidateList[candidateName]) throw new Error('"' +candidateName+ '" already exist in candidate list.');
this.candidateList[candidateName] = new Candidate(candidateName);
};
......@@ -74,23 +75,106 @@
}
};
RelativePlacement.prototype.addVotes = function () {};
RelativePlacement.prototype.getResult = function () {};
RelativePlacement.prototype.getResult = function () {
return relativePlacement(this.votes, this.candidateList);
};
/**
*
* @param name
* @param {String} name
* @constructor
* @property {String} name
* @property {Number[]} votes
* @property {Number[]} placements
* @property {Number[]} cumulativePlacement
*/
function Candidate(name) {
Object.defineProperties(this, {
name: {value: name, enumerable: true},
votes: {value:[], enumerable: true},
placements: {value:[], enumerable: true, writable: true}
placements: {value:[], enumerable: true, writable: true},
cumulativePlacement: {value:[], enumerable: true, writable: true}
});
}
/**
* @name relativePlacement
* @memberOf RelativePlacement
* @static
* @param {String[][]} votes
* @param {Object<String,Candidate>} [candidates]
* @returns {String[]}
*/
function relativePlacement(votes, candidates){
if(votes.length <= 1) return votes[0];
// Prepare candidate list if not pass
var init = false, majority, result, candidateNames;
if(!candidates) {
candidates = {};
init = true;
}
candidateNames = votes[0];
majority = Math.floor(votes.length / 2) + 1;
result = [];
candidateNames.forEach((name) => {
if(init) candidates[name] = new Candidate(name);
candidates[name].placements[candidateNames.length-1] = 0;
candidates[name].placements.fill(0,0,candidateNames.length-1);
candidates[name].cumulativePlacement[candidateNames.length-1] = 0;
candidates[name].cumulativePlacement.fill(0,0,candidateNames.length-1);
});
// Assign votes
votes.forEach((vote,num) => {
vote.forEach((name, place) => {
var candidate = candidates[name];
candidate.votes[num] = place;
for(let i = place; i < candidate.placements.length; i++){
candidate.placements[i]++;
candidate.cumulativePlacement[i] += place + 1;
}
});
});
// Search placement
var cursor = 0, nextPlace = 0, proposed = [];
while(result.length < candidateNames.length && cursor < candidateNames.length){
// Search majority
candidateNames.forEach(name => {
if(result.indexOf(name) != -1) return;
var candidate = candidates[name];
if(candidate.placements[cursor] >= majority) proposed.push(candidate);
});
if(proposed.length){
proposed.sort((a,b) => {
var sortCursor = cursor;
while(sortCursor < candidateNames.length){
if(a.placements[sortCursor] != b.placements[sortCursor])
return b.placements[sortCursor] - a.placements[sortCursor];
if(a.cumulativePlacement[sortCursor] != b.cumulativePlacement[sortCursor])
return a.cumulativePlacement[sortCursor] - b.cumulativePlacement[sortCursor];
sortCursor++;
}
return 0;
});
result = result.concat(proposed.map(candidate => candidate.name));
cursor = result.length;
proposed.splice(0);
continue;
}
cursor++;
}
return result;
}
RelativePlacement.relativePlacement = relativePlacement;
return RelativePlacement;
});
\ No newline at end of file
......
......@@ -33,7 +33,8 @@ describe('RelativePlacement global', () => {
assert.deepEqual({
name: 'foo',
votes:[],
placements: []
placements: [],
cumulativePlacement: []
}, election.candidateList['foo']);
});
......@@ -45,12 +46,14 @@ describe('RelativePlacement global', () => {
assert.deepEqual({
name: 'foo',
votes:[],
placements: []
placements: [],
cumulativePlacement: []
}, election.candidateList['foo']);
assert.deepEqual({
name: 'bar',
votes:[],
placements: []
placements: [],
cumulativePlacement: []
}, election.candidateList['bar']);
});
......
......@@ -2,16 +2,12 @@
* Created by Techniv on 02/12/2016.
*/
var assert = require('assert');
var RelativePlacement = require('../../relative-placement');
/** @type RelativePlacement.relativePlacement */
var relativePlacement = require('../../relative-placement').relativePlacement;
/** @var {TestData[]} */
var testDataList = require('./results_data.json');
describe('RelativePlacement algo', () => {
var election;
beforeEach(()=>{
election = new RelativePlacement();
});
describe('Process result data',() => {
......@@ -22,14 +18,11 @@ describe('RelativePlacement algo', () => {
*/
(testData,id) => {
it('#'+id+': '+(testData.comment||''), ()=>{
election.addCandidates(testData.result.concat().sort(()=>Math.random()-Math.random()));
var votes = compileVotes(testData);
votes.forEach((vote) => {
election.addVote(vote);
});
var result = relativePlacement(votes);
assert.deepEqual(election.getResult(), testData.result);
assert.deepEqual(result, testData.result);
});
}
);
......
......@@ -9,6 +9,18 @@
"result": ["A","B","C"]
},
{
"comment": "boogiebythebay.org study case",
"votes":{
"Couple 1":[1,1,3,2,3],
"Couple 2":[6,5,4,1,2],
"Couple 3":[2,4,1,5,5],
"Couple 4":[4,2,5,6,6],
"Couple 5":[5,6,2,3,4],
"Couple 6":[3,3,6,4,1]
},
"result": ["Couple 1","Couple 6","Couple 3","Couple 2","Couple 5","Couple 4"]
},
{
"comment": "Novice Strictly final",
"votes":{
"320": [1,1,1,1,1],
......