comparative-judgement
Version:
Comparative Judgement Algorithms
271 lines (257 loc) • 9.58 kB
JavaScript
/* jshint undef: false, strict: false */
// Turn off strict mode for this function so we can assign to global
(function (root,
factory) {
/*
AMD/CommonJS compatibility largely stolen from https://github.com/kriskowal/q/blob/master/q.js
*/
// This file will function properly as a <script> tag, or a module
// using CommonJS and NodeJS or RequireJS module formats. In
// Common/Node/RequireJS, the module exports the depres API and when
// executed as a simple <script>, it creates a depres global instead.
// CommonJS
if ("object" === typeof exports) {
module.exports = factory(require('underscore'));
// RequireJS
} else if ("function" === typeof define && define.amd) {
define(["underscore"], factory);
// <script>
} else {
root.cj = factory(root._);
}
})(this, function (_) {
"use strict";
// Chris's selection method
function selectionNonAdaptive(players) {
//shuffle and sort the players to get a pseudo-random distribution
players = _.sortBy(_.shuffle(players), function (player) {
return player.selected;
});
var player = players.shift();
var opponent;
//First decision? Choose the player with the next fewest decisions
if (player.selected <= 0) {
opponent = players.shift();
} else {
//Find a player not yet compared to
opponent = _.find(players, function (candidate) {
return !_.contains(candidate.opponents, player._id);
});
}
if (!opponent) {
//All matches have been made, make a random choice
opponent = players.shift();
}
// Update Judges Collection with current pair information
return [player, opponent];
}
function selectionAdaptive(players, thr, AP, offset) {
if (!offset) {
offset = 0;
} else {
offset = (_.random(1)==1 ? offset : -offset);
}
var statutils = require('..').statutils;
//assign true score of 0 if no true score
for (var i = 0; i < players.length; i++) {
if (!players[i].hasOwnProperty('trueScore')) {
players[i].trueScore = 0;
}
}
//thr = number of comparisons planned
//AP = acceleration parameter
thr = (thr ? thr : 20);
AP = (AP ? AP : 1);
//shuffle and sort the players to get a pseudo-random distribution
players = _.sortBy(_.shuffle(players), function (player) {
return player.selected;
});
var player = players.shift();
var opponent;
var opponents;
//First decision? Choose the opponent with the next fewest decisions
if (player.selected <= 0) {
opponent = players.shift();
} else {
// Calculate the weighted probabilities of selection
// Items should include all the true scores of candidates not yet compared to
// Find a player not yet compared to
opponents = _.filter(players, function (candidate) {
return !_.contains(candidate.opponents, player._id);
});
if (opponents.length === 0) {
// If all have been compared to, use all
// console.log('all opponents used up');
opponents = players;
} else {
//console.log('possible opponents',opponents.length);
}
var items = _.pluck(opponents, 'trueScore');
//console.log('items: ',items);
// Number of comparisons made by player
var done = player.comparisons;
// Get probabilities of selection
var theta = player.trueScore + offset;
//console.log('theta: ',theta);
var iPr = statutils.calciPR(theta, items, done, thr, AP);
//console.log('iPr: ',iPr);
// Choose opponent at random using vector of probabilties
var opponentIndex = statutils.sampleVector(iPr, Math.random());
opponent = opponents[opponentIndex];
}
//console.log('match: ',player._id,' v ',opponent._id);
return [player, opponent];
}
function chained(players,mx){
var player;
var opponent;
var rightChoices;
var leftChoices;
// Left hand side
// Filter out any unanchored
leftChoices = _.filter(players, function(pl){return (!pl.hasOwnProperty("anchorScore") );});
var seeds = _.filter(players, function(pl){return (pl.hasOwnProperty("anchorScore") );});
// Remove any players with greater than mx selected
leftChoices = _.filter(leftChoices, function(pl){return(pl.selected<mx);});
if(leftChoices.length>0){
// Shuffle and sort the players to get a pseudo-random distribution
leftChoices = _.sortBy(_.shuffle(leftChoices), function (player) {
return player.selected;
});
player = leftChoices.shift();
if(seeds.length>0){
if(player.selected===0){
// Seed decision
// Find a seed not yet compared to
// Shuffle and sort the players to get a pseudo-random distribution
seeds = _.sortBy(_.shuffle(seeds), function (player) {
return player.selected;
});
opponent = _.find(seeds, function (candidate) {return !_.contains(candidate.opponents, player._id);});
}
}
if(!opponent){
// No seed pair this time
// Not the same player or seed
rightChoices = _.filter(players, function(pl){return (pl._id!=player._id && !pl.hasOwnProperty("anchorScore"));});
// Not yet compared to
rightChoices = _.shuffle(rightChoices);
opponent = _.find(rightChoices, function (candidate) {
return !_.contains(candidate.opponents, player._id);
});
}
if(!opponent){
// Try a seed
// Not the same player but can be seed
rightChoices = _.filter(players, function(pl){return (pl._id!=player._id);});
// Not yet compared to
rightChoices = _.shuffle(rightChoices);
opponent = _.find(rightChoices, function (candidate) {
return !_.contains(candidate.opponents, player._id);
});
}
if(!opponent){
// Repeat a pair
rightChoices = _.filter(players, function(pl){return (pl._id!=player._id);});
// Not yet compared to
rightChoices = _.shuffle(rightChoices);
opponent = rightChoices.shift();
}
}
return [player, opponent];
}
function selectionSwiss(players) {
// Takes round & players
// First round – pair at random.
// Shuffle and sort the players to get a pseudo-random distribution
var sameRound;
var opponents;
players = _.sortBy(_.shuffle(players), function (player) {
return player.selected;
});
var player = players.shift();
var opponent;
//First decision? Choose the player with the next fewest decisions
if (player.selected <= 0) {
opponent = players.shift();
} else {
console.log('round: ', player.selected+1);
// Find a player not yet compared to who is on the same round
sameRound = _.filter(players, function (candidate) {
return (candidate.comparisons == player.comparisons);
});
if (sameRound.length === 0) {
// If all have fnished round, find any
sameRound = players;
}
console.log('same round players: ', sameRound.length);
// Find a player not uet compared to
// Find a player not yet compared to
opponents = _.filter(sameRound, function (candidate) {
return !_.contains(candidate.opponents, player._id);
});
console.log('of which not yet compared: ',opponents.length);
if (opponents.length === 0) {
// If all have been compared to, use all
console.log('all opponents used up');
opponents = players;
}
// Now we have opponents in the same round not yet compared to
// Find one with the same or closest score
// Calculate minimum absolute difference
opponents = _.sortBy(opponents, function (pl) {
return Math.abs(pl.observedScore - player.observedScore);
});
//console.log(player, opponents);
opponent = opponents.shift();
}
return [player, opponent];
}
function selectionByJudge(idJudge, players, decisions) {
// Every judge should see each possible pair
var statutils = require('..').statutils;
// List of ids
var pls = _.pluck(players, '_id');
// Every possible combination of ids
var k_combinations = statutils.k_combinations;
var combs = k_combinations(pls, 2);
// Filter decisions by judge
var dec = _.filter(decisions, function (decision) {
return decision.judge == idJudge;
});
//console.log('dec: ', dec);
// Get a list of all combinations so far
var pl1 = _.pluck(dec, 'chosen');
var pl2 = _.pluck(dec, 'notChosen');
//console.log('pl1: ', pl1);
//console.log('pl2: ', pl2);
var pairs = _.zip(pl1, pl2);
//console.log('combs: ', combs);
//console.log('pairs', pairs);
var diff = _.filter(combs, function (obj) {
return !_.findWhere(pairs, obj) & !_.findWhere(pairs, [obj[1], obj[0]]);
});
//If diff, then not all pairs have been selected
var pair;
if (diff.length > 0) {
pair = _.shuffle(diff).shift();
} else {
//If all pairs chosen, choose a random pair
pair = _.shuffle(combs).shift();
}
var player1 = _.findWhere(players, {
_id: pair[0]
});
var player2 = _.findWhere(players, {
_id: pair[1]
});
return [player1, player2];
}
return {
selectionAdaptive: selectionAdaptive,
selectionNonAdaptive: selectionNonAdaptive,
selectionByJudge: selectionByJudge,
selectionSwiss: selectionSwiss,
chained: chained,
};
});