From e31a174b716ee9fb6591213dd75a9f05e031d094 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Mon, 3 Apr 2023 12:21:28 +0200 Subject: [PATCH 1/3] changed method choice and details tag to account for weblate translations --- www/form/condorcet.js | 4 ++-- www/form/inner.js | 26 +++++++++++++++++--------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/www/form/condorcet.js b/www/form/condorcet.js index 38e6795e9..5006fbe42 100644 --- a/www/form/condorcet.js +++ b/www/form/condorcet.js @@ -202,10 +202,10 @@ define([], function () { var schulzeWinner = schulzeMethod(optionArray, listOfLists, _answers, uid); var rankedPairWinner = rankedPairsMethod(optionArray, listOfLists); var method = form[uid].condorcetmethod; - if (method === "Schulze") { + if (method === "schulze") { condorcetWinner[0] = schulzeWinner; condorcetWinner[1] = rankedPairWinner; - } else if (method === "Ranked Pairs") { + } else if (method === "ranked") { condorcetWinner[0] = rankedPairWinner; condorcetWinner[1] = schulzeWinner; } diff --git a/www/form/inner.js b/www/form/inner.js index 2257d8750..91c6c9834 100644 --- a/www/form/inner.js +++ b/www/form/inner.js @@ -77,7 +77,10 @@ define([ { Messages.form_showCondorcetMethod = "Condorcet method "; //XXX; + Messages.form_condorcetSchulze = "Schulze"; + Messages.form_condorcetRanked = "Ranked Pairs"; Messages.form_showCondorcetWinner = " winner: "; + Messages.form_showDetails = "Details"; Messages.form_condorcetExtendedDisplay = "Number of matches won by each candidate: "; var APP = window.APP = { @@ -2911,7 +2914,7 @@ define([ var condorcetResults = h('span'); var rankedResults = showCondorcetWinner(answers, block.opts, uid, form)[0][0]; - if (form[uid].condorcetmethod === 'Schulze') { + if (form[uid].condorcetmethod === 'schulze') { condorcetWinner = rankedResults[Object.keys(rankedResults).length - 1]; condorcetWinner.join(', '); condorcetWinner.forEach(function(option) { @@ -2922,7 +2925,7 @@ define([ return rankedResults[result] + ' : ' + result; }); return [condorcetResults, detailedResults]; - } else if (form[uid].condorcetmethod === 'Ranked Pairs') { + } else if (form[uid].condorcetmethod === 'ranked') { var sortedRankDict = showCondorcetWinner(answers, block.opts, uid, form)[0][1]; condorcetWinner = rankedResults[0]; @@ -2934,7 +2937,7 @@ define([ }; - var dropdownOpts = ['Schulze', 'Ranked Pairs']; + var dropdownOpts = [Messages.form_condorcetSchulze, Messages.form_condorcetRanked]; var options = dropdownOpts.map(function (t) { return { @@ -2955,9 +2958,13 @@ define([ buttonCls: 'btn btn-secondary' }; var typeSelect = UIElements.createDropdown(dropdownConfig); - - form[uid].condorcetmethod = 'Schulze'; - typeSelect.setValue(form[uid].condorcetmethod); + + typeSelect.setValue(dropdownOpts[0]); + + var methodOptions = {0: 'schulze', 1: 'ranked'}; + var optionIndex = dropdownOpts.indexOf(typeSelect.getValue()); + + form[uid].condorcetmethod = methodOptions[optionIndex]; var method = h('div.cp-dropdown-container', typeSelect[0]); @@ -2970,12 +2977,13 @@ define([ var condorcetWinner = h('span', { id: 'cW'}, calculateCondorcet()[0]); condorcetWinnerDiv = h('div.cp-form-edit-type'); - var detailsDiv = h('details', {id: 'dD'}, Messages.form_condorcetExtendedDisplay, h('div', calculateCondorcet()[1].join(', '))); + var detailsDiv = h('details', h('summary', Messages.form_showDetails), {id: 'dD'}, Messages.form_condorcetExtendedDisplay, h('div', calculateCondorcet()[1].join(', '))); $selector.click(function () { - form[uid].condorcetmethod = $(this).attr('data-value'); + optionIndex = dropdownOpts.indexOf($(this).attr('data-value')); + form[uid].condorcetmethod = methodOptions[optionIndex]; $('#cW').replaceWith(h('span', { id: 'cW'}, calculateCondorcet()[0])); - $('#dD').replaceWith(h('details', {id: 'dD'}, Messages.form_condorcetExtendedDisplay, h('div', calculateCondorcet()[1].join(', ')))); + $('#dD').replaceWith(h('details', h('summary', Messages.form_showDetails), {id: 'dD'}, Messages.form_condorcetExtendedDisplay, h('div', calculateCondorcet()[1].join(', ')))); }); condorcetWinnerDiv.append(h('div', Messages.form_showCondorcetMethod, method, Messages.form_showCondorcetWinner, condorcetWinner, detailsDiv, {style: {margin: '10px'}})); From 10d44ca52ecfa9b955f5765b4b00f067ad5f9ed2 Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Wed, 5 Apr 2023 12:57:23 +0200 Subject: [PATCH 2/3] tweaked to account for empty answers --- www/form/condorcet.js | 7 ++----- www/form/inner.js | 16 +++++++++++----- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/www/form/condorcet.js b/www/form/condorcet.js index 5006fbe42..f2581894b 100644 --- a/www/form/condorcet.js +++ b/www/form/condorcet.js @@ -28,7 +28,6 @@ define([], function () { var comparePairs = function() { var pairs = getPermutations(optionArray, 2); - var pairDict = {}; pairs.forEach(function (pair) { pairDict[pair] = 0; @@ -203,11 +202,9 @@ define([], function () { var rankedPairWinner = rankedPairsMethod(optionArray, listOfLists); var method = form[uid].condorcetmethod; if (method === "schulze") { - condorcetWinner[0] = schulzeWinner; - condorcetWinner[1] = rankedPairWinner; + condorcetWinner = schulzeWinner; } else if (method === "ranked") { - condorcetWinner[0] = rankedPairWinner; - condorcetWinner[1] = schulzeWinner; + condorcetWinner = rankedPairWinner; } return condorcetWinner; }; diff --git a/www/form/inner.js b/www/form/inner.js index 91c6c9834..09e5923c6 100644 --- a/www/form/inner.js +++ b/www/form/inner.js @@ -2897,22 +2897,28 @@ define([ var listOfLists = []; Object.keys(_answers).forEach(function(a) { - listOfLists.push(_answers[a].msg[uid]); + if (_answers[a].msg[uid] !== undefined) { + listOfLists.push(_answers[a].msg[uid]); + } + }); - return Condorcet.showCondorcetWinner(_answers, opts, uid, form, optionArray, listOfLists); + + if (Object.keys(listOfLists).length !== 0) { + return Condorcet.showCondorcetWinner(_answers, opts, uid, form, optionArray, listOfLists); + } }; var condorcetWinnerDiv = h('div.cp-form-block-content'); if (type === "sort") { - if (summary) { + if (summary && showCondorcetWinner(answers, block.opts, uid, form) !== undefined) { var calculateCondorcet = function() { var condorcetWinner; var detailedResults; var condorcetResults = h('span'); - var rankedResults = showCondorcetWinner(answers, block.opts, uid, form)[0][0]; + var rankedResults = showCondorcetWinner(answers, block.opts, uid, form)[0]; if (form[uid].condorcetmethod === 'schulze') { condorcetWinner = rankedResults[Object.keys(rankedResults).length - 1]; @@ -2926,7 +2932,7 @@ define([ }); return [condorcetResults, detailedResults]; } else if (form[uid].condorcetmethod === 'ranked') { - var sortedRankDict = showCondorcetWinner(answers, block.opts, uid, form)[0][1]; + var sortedRankDict = showCondorcetWinner(answers, block.opts, uid, form)[1]; condorcetWinner = rankedResults[0]; detailedResults = Object.keys(sortedRankDict).map(function(result) { From e0c45578a597218eb7f832517a4f56802208745b Mon Sep 17 00:00:00 2001 From: zuzanna-maria Date: Tue, 18 Apr 2023 11:59:30 +0200 Subject: [PATCH 3/3] logic re-write and refactoring --- www/form/condorcet.js | 224 ++++++++++++++++++++++++++++++------------ www/form/inner.js | 153 ++++++++++++++--------------- 2 files changed, 233 insertions(+), 144 deletions(-) diff --git a/www/form/condorcet.js b/www/form/condorcet.js index f2581894b..6f8bd87e3 100644 --- a/www/form/condorcet.js +++ b/www/form/condorcet.js @@ -25,8 +25,9 @@ define([], function () { }; Condorcet.showCondorcetWinner = function(_answers, opts, uid, form, optionArray, listOfLists) { - + var comparePairs = function() { + var pairs = getPermutations(optionArray, 2); var pairDict = {}; pairs.forEach(function (pair) { @@ -37,7 +38,7 @@ define([], function () { } }); }); - + var pathDictionary = {}; //Adds winner of each pairwise comparison to path optionArray.forEach(function(option1) { @@ -59,8 +60,9 @@ define([], function () { var schulzeMethod = function(optionArray) { var findWeakestPath = function(optionArray) { + var pathDictionary = comparePairs(optionArray); - + //Iterates through paths between two options comparing the weakest edge to current score optionArray.forEach(function(option1) { optionArray.forEach(function(option2) { @@ -97,12 +99,14 @@ define([], function () { } }); }); + return pathDictionary; }; var calculateWinner = function() { var pathDictionary = findWeakestPath(optionArray); + //Calculate scores for each option and select winner var winningMatches = {}; optionArray.forEach(function(option){ @@ -110,9 +114,16 @@ define([], function () { }); Object.keys(pathDictionary).forEach(function(pair) { var option1 = pair.split(',')[0]; + var option2 = pair.split(',')[1]; winningMatches[option1] ++; + winningMatches[option2] --; + + Object.keys(pathDictionary).forEach(function(p) { + if (p.split(',')[1] === option1 && pathDictionary[p] > pathDictionary[pair]) { + winningMatches[option1] --; + } + }); }); - var rankedResults = {}; Object.keys(winningMatches).forEach(function(option) { if (Object.keys(rankedResults).includes(winningMatches[option].toString())) { @@ -121,79 +132,164 @@ define([], function () { rankedResults[winningMatches[option]] = [option]; } }); - return [rankedResults, winningMatches]; + + var losing = []; + var winning = []; + var winningPairs = []; + Object.keys(pathDictionary).forEach(function(pair) { + var option1 = pair.split(',')[0]; + var option2 = pair.split(',')[1]; + + losing.push(option2); + winning.push(option1); + + }); + Object.keys(pathDictionary).forEach(function(pair) { + var option1 = pair.split(',')[0]; + if (!losing.includes(option1)) { + winningPairs.push(option1); + } + }); + + var winner; + var winnersArray = []; + + winningPairs.forEach(function(pair) { + if (!winnersArray.includes(pair)) { + winnersArray.push(pair); + } + }); + + if (winnersArray.length !== 0) { + winner = winnersArray; + } else if (winnersArray.length === 0 && Object.keys(rankedResults).length === 1) { + winner = []; + } else { + winner = rankedResults[0]; + } + + return [winner, rankedResults]; }; return(calculateWinner()); }; - var rankedPairsMethod = function (optionArray, listOfLists, _answers) { + var rankedPairsMethod = function (optionArray, listOfLists) { var pairs = getPermutations(optionArray, 2); - var lockPairs = function() { - //'Locks' pairwise comparisons which do not create a beatpath cycle - var pathDictionary = comparePairs(listOfLists, pairs); - - var items = Object.keys(pathDictionary).map(function(key) { - return [key, pathDictionary[key]]; - }); + //'Locks' pairwise comparisons which do not create a beatpath cycle + var pathDictionary = comparePairs(listOfLists, pairs); + + var items = Object.keys(pathDictionary).sort().map(function(key) { + return [key, pathDictionary[key]]; + }); + var sortedItems = {}; + Object.values(items).forEach(function(value) { + if (Object.keys(sortedItems).includes(value[1].toString())) { + sortedItems[value[1]].push(value[0]); + } else { + sortedItems[value[1]] = []; + sortedItems[value[1]].push(value[0]); + } - items.sort(function(first, second) { - return second[1] - first[1]; + }); + + var rankingDict = {}; + var winning = []; + var losing = []; + + Object.values(sortedItems).forEach(function(v) { + v.forEach(function(val){ + winning.push(val.split(',')[0]); + losing.push(val.split(',')[1]); }); - - var list = [items[0][0]]; - var list2 = []; - items.forEach(function(item){ - opts = item[0].split(','); - if (!list2.includes(opts[0])) { - list2.push(opts[1]); - if (!list.includes(item[0])) { - list.push(item[0]); - } - } + }); + + + optionArray.forEach(function(option){ + rankingDict[option] = 0; + }); + + var rankingArray = []; + Object.values(rankingDict).forEach(function(pair) { + if (!rankingArray .includes(pair)) { + rankingArray .push(pair); + } + }); + var winner; + if (rankingArray.length <= 1) { + optionArray.forEach(function(option){ + if (winning.includes(option)) { + rankingDict[option] ++; + } + if (losing.includes(option)) { + rankingDict[option] --; + } }); - return list; - - }; - - var rankPairs = function(optionArray, listOfLists, _answers) { - //Ranks locked pairs - var list = lockPairs(optionArray, listOfLists, _answers); - - var rankDict = {}; - optionArray.forEach(function(o){ - rankDict[o] = 0; - list.forEach(function(pair) { - if (o === pair.split(',')[1]) { - rankDict[o] --; - } else if (o === pair.split(',')[0]) { - rankDict[o] ++; - } + } + + var rankedResults = {}; + Object.keys(rankingDict).forEach(function(option) { + if (Object.keys(rankedResults).includes(rankingDict[option].toString())) { + rankedResults[rankingDict[option]].push(option); + } else { + rankedResults[rankingDict[option]] = [option]; + } + }); + + var rankedKeys = Object.keys(rankedResults).sort().map(function(key) { + return [key, rankedResults[key]]; + }); + + var finalsortedItems = {}; + Object.values(rankedKeys).forEach(function(value){ + if (Object.keys(finalsortedItems).includes(value[1].toString())) { + finalsortedItems[value[1]].push(value[0]); + } else { + finalsortedItems[value[1]] = []; + finalsortedItems[value[1]].push(value[0]); + } + }); + + var finalRankingArray = []; + Object.values(rankingDict).forEach(function(pair) { + if (!finalRankingArray.includes(pair)) { + finalRankingArray.push(pair); + } + }); + + if (finalRankingArray.length > 1) { + var maxScore = Math.max.apply(null, Object.keys(rankedResults)); + winner = rankedResults[maxScore]; + } else if (Object.keys(sortedItems).length === 0) { + winner = []; + } else { + var index = Array.apply(null, Array(Object.keys(sortedItems).length-1)).map(function (_, i) { return i; }); + index.forEach(function(i) { + pairs = Object.values(sortedItems).reverse()[i]; + pairs.forEach(function(pair) { + var valIndex = winning.indexOf(pair.split(',')[1]); + winning.splice(valIndex, 1); }); }); - - var rankedItems = Object.keys(rankDict).map(function(key) { - return [key, rankDict[key]]; - }); - - rankedItems.sort(function(first, second) { - return second[1] - first[1]; - }); - - var finalRank = []; - rankedItems.forEach(function(i){ - finalRank.push(i[0]); - }); - var sortedRankDict = {}; - finalRank.forEach(function(opt){ - sortedRankDict[opt] = rankDict[opt]; - }); - return [finalRank, sortedRankDict]; - }; - var condorcetWinner = rankPairs(optionArray, listOfLists, _answers); - return condorcetWinner; + var winnerArray = []; + winning.forEach(function(option){ + if (!winnerArray.includes(option)) { + winnerArray.push(option); + } + }); + + if (winnerArray.length === optionArray.length) { + winner = []; + } else { + winner = winnerArray; + } + + } + + return [winner, rankedResults]; + }; var pickMethod = function(optionArray, listOfLists, _answers, uid){ diff --git a/www/form/inner.js b/www/form/inner.js index 09e5923c6..13fe80064 100644 --- a/www/form/inner.js +++ b/www/form/inner.js @@ -82,6 +82,7 @@ define([ Messages.form_showCondorcetWinner = " winner: "; Messages.form_showDetails = "Details"; Messages.form_condorcetExtendedDisplay = "Number of matches won by each candidate: "; + Messages.form_noCondorcetWinner = "No winner"; var APP = window.APP = { blocks: {} @@ -2911,91 +2912,83 @@ define([ var condorcetWinnerDiv = h('div.cp-form-block-content'); - if (type === "sort") { - if (summary && showCondorcetWinner(answers, block.opts, uid, form) !== undefined) { + if (type === "sort" && summary && showCondorcetWinner(answers, block.opts, uid, form) !== undefined) { + var calculateCondorcet = function() { + var condorcetResults = h('span'); + if (showCondorcetWinner(answers, block.opts, uid, form) !== undefined) { + var rankedResults = showCondorcetWinner(answers, block.opts, uid, form)[1]; + var condorcetWinner = showCondorcetWinner(answers, block.opts, uid, form)[0]; + if (condorcetWinner.length > 1) { + condorcetResults.append(h('span', condorcetWinner.join(', '))); + } else if (condorcetWinner.length === 1 ) { + condorcetResults.append(h('span', condorcetWinner)); + } else { + condorcetResults.append(h('span', Messages.form_noCondorcetWinner)); + } - var calculateCondorcet = function() { - var condorcetWinner; - var detailedResults; - var condorcetResults = h('span'); - var rankedResults = showCondorcetWinner(answers, block.opts, uid, form)[0]; - - if (form[uid].condorcetmethod === 'schulze') { - condorcetWinner = rankedResults[Object.keys(rankedResults).length - 1]; - condorcetWinner.join(', '); - condorcetWinner.forEach(function(option) { - condorcetResults.append(h('span', option)); - }); - - detailedResults = Object.keys(rankedResults).reverse().map(function(result) { - return rankedResults[result] + ' : ' + result; - }); - return [condorcetResults, detailedResults]; - } else if (form[uid].condorcetmethod === 'ranked') { - var sortedRankDict = showCondorcetWinner(answers, block.opts, uid, form)[1]; - condorcetWinner = rankedResults[0]; - - detailedResults = Object.keys(sortedRankDict).map(function(result) { - return result + ' : ' + sortedRankDict[result]; - }); + var detailedResults = Object.keys(rankedResults).sort(function(a,b) { return a - b; }).reverse().map(function(result) { + if (rankedResults[result].length > 1) { + return rankedResults[result].join(', ') + ' : ' + result; + } else { + return rankedResults[result] + ' : ' + result; } - return [condorcetWinner, detailedResults]; - }; - - - var dropdownOpts = [Messages.form_condorcetSchulze, Messages.form_condorcetRanked]; - - var options = dropdownOpts.map(function (t) { - return { - tag: 'a', - attributes: { - 'class': 'cp-form-type-value', - 'data-value': t, - 'href': '#', - }, - content: t - }; }); - var dropdownConfig = { - text: '', // Button initial text - options: options, - isSelect: true, - caretDown: true, - buttonCls: 'btn btn-secondary' - }; - var typeSelect = UIElements.createDropdown(dropdownConfig); + return [condorcetResults, detailedResults]; + }}; + - typeSelect.setValue(dropdownOpts[0]); - - var methodOptions = {0: 'schulze', 1: 'ranked'}; - var optionIndex = dropdownOpts.indexOf(typeSelect.getValue()); - + var dropdownOpts = [Messages.form_condorcetSchulze, Messages.form_condorcetRanked]; + + var options = dropdownOpts.map(function (t) { + return { + tag: 'a', + attributes: { + 'class': 'cp-form-type-value', + 'data-value': t, + 'href': '#', + }, + content: t + }; + }); + var dropdownConfig = { + text: '', // Button initial text + options: options, + isSelect: true, + caretDown: true, + buttonCls: 'btn btn-secondary' + }; + var typeSelect = UIElements.createDropdown(dropdownConfig); + + typeSelect.setValue(dropdownOpts[0]); + + var methodOptions = {0: 'schulze', 1: 'ranked'}; + var optionIndex = dropdownOpts.indexOf(typeSelect.getValue()); + + form[uid].condorcetmethod = methodOptions[optionIndex]; + + var method = h('div.cp-dropdown-container', typeSelect[0]); + + var evOnSave = Util.mkEvent(); + typeSelect.onChange.reg(evOnSave.fire); + var $typeSelect = $(typeSelect); + + var $selector = $typeSelect.find('a'); + + var condorcetWinner = h('span', { id: 'cW'}, calculateCondorcet()[0]); + condorcetWinnerDiv = h('div.cp-form-edit-type'); + + var detailsDiv = h('details', h('summary', Messages.form_showDetails), {id: 'dD'}, Messages.form_condorcetExtendedDisplay, h('div', calculateCondorcet()[1].join(', '))); + + $selector.click(function () { + optionIndex = dropdownOpts.indexOf($(this).attr('data-value')); form[uid].condorcetmethod = methodOptions[optionIndex]; - - var method = h('div.cp-dropdown-container', typeSelect[0]); - - var evOnSave = Util.mkEvent(); - typeSelect.onChange.reg(evOnSave.fire); - var $typeSelect = $(typeSelect); - - var $selector = $typeSelect.find('a'); - - var condorcetWinner = h('span', { id: 'cW'}, calculateCondorcet()[0]); - condorcetWinnerDiv = h('div.cp-form-edit-type'); - - var detailsDiv = h('details', h('summary', Messages.form_showDetails), {id: 'dD'}, Messages.form_condorcetExtendedDisplay, h('div', calculateCondorcet()[1].join(', '))); - - $selector.click(function () { - optionIndex = dropdownOpts.indexOf($(this).attr('data-value')); - form[uid].condorcetmethod = methodOptions[optionIndex]; - $('#cW').replaceWith(h('span', { id: 'cW'}, calculateCondorcet()[0])); - $('#dD').replaceWith(h('details', h('summary', Messages.form_showDetails), {id: 'dD'}, Messages.form_condorcetExtendedDisplay, h('div', calculateCondorcet()[1].join(', ')))); - }); - - condorcetWinnerDiv.append(h('div', Messages.form_showCondorcetMethod, method, Messages.form_showCondorcetWinner, condorcetWinner, detailsDiv, {style: {margin: '10px'}})); - - } - } + $('#cW').replaceWith(h('span', { id: 'cW'}, calculateCondorcet()[0])); + $('#dD').replaceWith(h('details', h('summary', Messages.form_showDetails), {id: 'dD'}, Messages.form_condorcetExtendedDisplay, h('div', calculateCondorcet()[1].join(', ')))); + }); + + condorcetWinnerDiv.append(h('div', Messages.form_showCondorcetMethod, method, Messages.form_showCondorcetWinner, condorcetWinner, detailsDiv, {style: {margin: '10px'}})); + + } var q = h('div.cp-form-block-question', block.q || Messages.form_default);