Commit 50001c8a 50001c8a924e40b23994164b25b2d60b57c38d3c by Vincent Peybernes

#4 Implement Relative Placement

1 parent 287e472b
Pipeline #5 for 50001c8a passed in 33 seconds
1 /** 1 /**
2 * Created by Techniv on 30/11/2016. 2 * Created by Techniv on 30/11/2016.
3 * @module relative_placement
3 */ 4 */
4 (function (define) { 5 (function (define) {
5 // TODO define exhaustive module platform
6 module.exports = define(); 6 module.exports = define();
7 })(function(){ 7 })(function(){
8 8
9 /** 9 /**
10 * 10 * @name RelativePlacement
11 * @constructor 11 * @constructor
12 * @property {Candidate[]} candidateList 12 * @property {Candidate[]} candidateList
13 * @property {String[]} votes 13 * @property {String[][]} votes
14 * @property {Number} numberOfCandidates 14 * @property {Number} numberOfCandidates
15 * 15 *
16 */ 16 */
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
29 * @this RelativePlacement 29 * @this RelativePlacement
30 */ 30 */
31 RelativePlacement.prototype.addCandidate = function addCandidate(candidateName){ 31 RelativePlacement.prototype.addCandidate = function addCandidate(candidateName){
32 if(this.votes.length) throw new Error("Adding candidates after votes is not permit.");
32 if(this.candidateList[candidateName]) throw new Error('"' +candidateName+ '" already exist in candidate list.'); 33 if(this.candidateList[candidateName]) throw new Error('"' +candidateName+ '" already exist in candidate list.');
33 this.candidateList[candidateName] = new Candidate(candidateName); 34 this.candidateList[candidateName] = new Candidate(candidateName);
34 }; 35 };
...@@ -74,23 +75,106 @@ ...@@ -74,23 +75,106 @@
74 } 75 }
75 }; 76 };
76 RelativePlacement.prototype.addVotes = function () {}; 77 RelativePlacement.prototype.addVotes = function () {};
77 RelativePlacement.prototype.getResult = function () {}; 78 RelativePlacement.prototype.getResult = function () {
79 return relativePlacement(this.votes, this.candidateList);
80 };
78 81
79 /** 82 /**
80 * 83 *
81 * @param name 84 * @param {String} name
82 * @constructor 85 * @constructor
83 * @property {String} name 86 * @property {String} name
84 * @property {Number[]} votes 87 * @property {Number[]} votes
85 * @property {Number[]} placements 88 * @property {Number[]} placements
89 * @property {Number[]} cumulativePlacement
86 */ 90 */
87 function Candidate(name) { 91 function Candidate(name) {
88 Object.defineProperties(this, { 92 Object.defineProperties(this, {
89 name: {value: name, enumerable: true}, 93 name: {value: name, enumerable: true},
90 votes: {value:[], enumerable: true}, 94 votes: {value:[], enumerable: true},
91 placements: {value:[], enumerable: true, writable: true} 95 placements: {value:[], enumerable: true, writable: true},
96 cumulativePlacement: {value:[], enumerable: true, writable: true}
97 });
98 }
99
100 /**
101 * @name relativePlacement
102 * @memberOf RelativePlacement
103 * @static
104 * @param {String[][]} votes
105 * @param {Object<String,Candidate>} [candidates]
106 * @returns {String[]}
107 */
108 function relativePlacement(votes, candidates){
109 if(votes.length <= 1) return votes[0];
110
111 // Prepare candidate list if not pass
112 var init = false, majority, result, candidateNames;
113 if(!candidates) {
114 candidates = {};
115 init = true;
116 }
117 candidateNames = votes[0];
118 majority = Math.floor(votes.length / 2) + 1;
119 result = [];
120
121 candidateNames.forEach((name) => {
122 if(init) candidates[name] = new Candidate(name);
123 candidates[name].placements[candidateNames.length-1] = 0;
124 candidates[name].placements.fill(0,0,candidateNames.length-1);
125 candidates[name].cumulativePlacement[candidateNames.length-1] = 0;
126 candidates[name].cumulativePlacement.fill(0,0,candidateNames.length-1);
92 }); 127 });
128
129 // Assign votes
130 votes.forEach((vote,num) => {
131 vote.forEach((name, place) => {
132 var candidate = candidates[name];
133 candidate.votes[num] = place;
134 for(let i = place; i < candidate.placements.length; i++){
135 candidate.placements[i]++;
136 candidate.cumulativePlacement[i] += place + 1;
137 }
138 });
139 });
140
141 // Search placement
142 var cursor = 0, nextPlace = 0, proposed = [];
143 while(result.length < candidateNames.length && cursor < candidateNames.length){
144
145 // Search majority
146 candidateNames.forEach(name => {
147 if(result.indexOf(name) != -1) return;
148 var candidate = candidates[name];
149 if(candidate.placements[cursor] >= majority) proposed.push(candidate);
150 });
151
152 if(proposed.length){
153 proposed.sort((a,b) => {
154 var sortCursor = cursor;
155 while(sortCursor < candidateNames.length){
156 if(a.placements[sortCursor] != b.placements[sortCursor])
157 return b.placements[sortCursor] - a.placements[sortCursor];
158 if(a.cumulativePlacement[sortCursor] != b.cumulativePlacement[sortCursor])
159 return a.cumulativePlacement[sortCursor] - b.cumulativePlacement[sortCursor];
160
161 sortCursor++;
162 }
163 return 0;
164 });
165 result = result.concat(proposed.map(candidate => candidate.name));
166 cursor = result.length;
167 proposed.splice(0);
168 continue;
169 }
170
171 cursor++;
172 }
173
174
175 return result;
93 } 176 }
94 177
178 RelativePlacement.relativePlacement = relativePlacement;
95 return RelativePlacement; 179 return RelativePlacement;
96 }); 180 });
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -33,7 +33,8 @@ describe('RelativePlacement global', () => { ...@@ -33,7 +33,8 @@ describe('RelativePlacement global', () => {
33 assert.deepEqual({ 33 assert.deepEqual({
34 name: 'foo', 34 name: 'foo',
35 votes:[], 35 votes:[],
36 placements: [] 36 placements: [],
37 cumulativePlacement: []
37 }, election.candidateList['foo']); 38 }, election.candidateList['foo']);
38 }); 39 });
39 40
...@@ -45,12 +46,14 @@ describe('RelativePlacement global', () => { ...@@ -45,12 +46,14 @@ describe('RelativePlacement global', () => {
45 assert.deepEqual({ 46 assert.deepEqual({
46 name: 'foo', 47 name: 'foo',
47 votes:[], 48 votes:[],
48 placements: [] 49 placements: [],
50 cumulativePlacement: []
49 }, election.candidateList['foo']); 51 }, election.candidateList['foo']);
50 assert.deepEqual({ 52 assert.deepEqual({
51 name: 'bar', 53 name: 'bar',
52 votes:[], 54 votes:[],
53 placements: [] 55 placements: [],
56 cumulativePlacement: []
54 }, election.candidateList['bar']); 57 }, election.candidateList['bar']);
55 }); 58 });
56 59
......
...@@ -2,16 +2,12 @@ ...@@ -2,16 +2,12 @@
2 * Created by Techniv on 02/12/2016. 2 * Created by Techniv on 02/12/2016.
3 */ 3 */
4 var assert = require('assert'); 4 var assert = require('assert');
5 var RelativePlacement = require('../../relative-placement'); 5 /** @type RelativePlacement.relativePlacement */
6 var relativePlacement = require('../../relative-placement').relativePlacement;
6 /** @var {TestData[]} */ 7 /** @var {TestData[]} */
7 var testDataList = require('./results_data.json'); 8 var testDataList = require('./results_data.json');
8 describe('RelativePlacement algo', () => { 9 describe('RelativePlacement algo', () => {
9 10
10 var election;
11
12 beforeEach(()=>{
13 election = new RelativePlacement();
14 });
15 11
16 describe('Process result data',() => { 12 describe('Process result data',() => {
17 13
...@@ -22,14 +18,11 @@ describe('RelativePlacement algo', () => { ...@@ -22,14 +18,11 @@ describe('RelativePlacement algo', () => {
22 */ 18 */
23 (testData,id) => { 19 (testData,id) => {
24 it('#'+id+': '+(testData.comment||''), ()=>{ 20 it('#'+id+': '+(testData.comment||''), ()=>{
25 election.addCandidates(testData.result.concat().sort(()=>Math.random()-Math.random()));
26 var votes = compileVotes(testData); 21 var votes = compileVotes(testData);
27 22
28 votes.forEach((vote) => { 23 var result = relativePlacement(votes);
29 election.addVote(vote);
30 });
31 24
32 assert.deepEqual(election.getResult(), testData.result); 25 assert.deepEqual(result, testData.result);
33 }); 26 });
34 } 27 }
35 ); 28 );
......
...@@ -9,6 +9,18 @@ ...@@ -9,6 +9,18 @@
9 "result": ["A","B","C"] 9 "result": ["A","B","C"]
10 }, 10 },
11 { 11 {
12 "comment": "boogiebythebay.org study case",
13 "votes":{
14 "Couple 1":[1,1,3,2,3],
15 "Couple 2":[6,5,4,1,2],
16 "Couple 3":[2,4,1,5,5],
17 "Couple 4":[4,2,5,6,6],
18 "Couple 5":[5,6,2,3,4],
19 "Couple 6":[3,3,6,4,1]
20 },
21 "result": ["Couple 1","Couple 6","Couple 3","Couple 2","Couple 5","Couple 4"]
22 },
23 {
12 "comment": "Novice Strictly final", 24 "comment": "Novice Strictly final",
13 "votes":{ 25 "votes":{
14 "320": [1,1,1,1,1], 26 "320": [1,1,1,1,1],
......