Tournament pairing algorhitm - javascript

I need to find ideal pairing amongst tournament players based on following rules:
players with equal points score or similar should be matched
two players can have only one mutual match in tournament
all players must have a match in a round
Its basically a simplified Swiss tournament system.
I have followings standings:
[{
"playerIndex": 0,
"points": 0,
"opponents": [3, 2, 4]
}, {
"playerIndex": 1,
"points": 3,
"opponents": [4, 5, 2]
}, {
"playerIndex": 2,
"points": 3,
"opponents": [5, 0, 1]
}, {
"playerIndex": 3,
"points": 4,
"opponents": [0, 4, 5]
}, {
"playerIndex": 4,
"points": 6,
"opponents": [1, 3, 0]
}, {
"playerIndex": 5,
"points": 2,
"opponents": [2, 1, 3]
}]
Read as: first array item is player number (index) 0 that already played with players number (index) 3, 2 and 4 and gained 0 points, each item for one of six players in a tournament.
And I need to pick three matches for the fourth match. Following a rule that no two players can play a mutual match more than once I choose from following matches:
[ [ 0, 1 ], [ 0, 5 ], [ 1, 3 ], [ 2, 3 ], [ 2, 4 ], [ 4, 5 ] ]
Each of these six possible matches has a points difference between the two players as follows:
[3, 2, 1, 1, 2, 4]
So ideal pairing for the fourth round that gives each player a match in a round with lowest points difference between paired players is:
[ [0, 5], [1, 3], [2, 4] ]
Is there any way of finding these ideal pairings in real time? It is impossible to try all the possible combinations, because there can be more than 100 people in a tournament and the calculations would take forever.
I have been advised to use https://en.wikipedia.org/wiki/Edmonds%27_algorithm or https://en.wikipedia.org/wiki/Edmonds%E2%80%93Karp_algorithm (both available in JS: https://www.npmjs.com/package/edmonds-blossom and https://github.com/sfentress/edmunds-karp). But I am not sure how to read the results.
Can somebody help please?

Edit: Hungarian algorithm fails if there is too many possible solutions. In my case after first round when there is a lot of players with same amount of points.
Edmond Blossoms algorithm performs much better (found this JS implementation available via NPM https://github.com/mattkrick/EdmondsBlossom).
Just had trouble understanding how to use it. The main difference is that you need to feed it with pairs and the points difference between pairs is higher for the pairs that should be preferred. So I use zero difference for pairs that already played before.
My final (hopefully) solution:
var maxDiff = (roundIndex + 1) * this.config.pointsWin
var possiblePairs = []
availablePlayers.forEach(player => {
availablePlayers.forEach(opponent => {
if (
player.playerIndex !== opponent.playerIndex // &&
// player.opponents.indexOf(opponent.playerIndex) === -1
) {
var match = [player.playerIndex, opponent.playerIndex]
match.sort(function(a, b) {
return a - b;
})
if (player.opponents.indexOf(opponent.playerIndex) === -1) {
match.push(maxDiff - Math.abs(player.points - opponent.points))
}
else {
match.push(0)
}
if (this.searchForArray(possiblePairs, match) === -1) {
possiblePairs.push(match)
}
}
})
})
var rawPairing = edmondsBlossom(possiblePairs)
rawPairing.forEach((match, index) => {
if (match !== -1 && match < index) {
round.matches.push({
home: match,
home_score: '',
away: index,
away_score: '',
referee: -1
})
}
})
First I count max possible points difference amongst players (expecting someone could gain zero points and someone else all of them). Then create all possible combinations between players and mark them with MAX POSSIBLE POINTS - PLAYERS POINTS DIFFERENCE or ZERO for players that matched before.
Then feed the array to EdmondsBlossom function that returns array of integers...
[6,8,14,5,15,3,0,10,1,12,7,13,9,11,2,4]
...read as follows: player index 0 should be paired with player 6, player 1 vs 8, player 2 vs 14, player 3 vs 5... player 5 vs 3 (duplicates). Sometimes there is -1 value in the output that is simply skipped.
Here is my solution (deprecated):
Thanks to #VedPrakash's comment I found the Hungarian algorithm that solves my problem. Luckily there is also a JS implementation available on NPM https://github.com/addaleax/munkres-js.
The Munkers function needs a matrix as input. In my case it is players points difference on intersections of my matrix (see below). The pairs that already played each other have higher value that cant be achieved (9 in my case).
Input matrix:
[ [ 9, 4, 9, 9, 9, 3 ],
[ 4, 9, 9, 2, 9, 9 ],
[ 9, 9, 9, 2, 4, 9 ],
[ 9, 2, 2, 9, 9, 9 ],
[ 9, 9, 4, 9, 9, 5 ],
[ 3, 9, 9, 9, 5, 9 ] ]
Output:
[ [ 0, 5 ], [ 1, 3 ], [ 2, 4 ], [ 3, 1 ], [ 4, 2 ], [ 5, 0 ] ]
The last thing to take care of is filter the Munkers output (that contains duplicates - both pairs 0vs1 and 1vs0) so i filter them simply by comparing first and second index.
My implementation:
var maxDiff = (roundIndex + 1) * this.config.pointsWin
// prepare matrix
var matrix = [];
for (var i = 0; i < availablePlayers.length; i++) {
matrix[i] = new Array(availablePlayers.length);
matrix[i].fill(0)
}
// fill matrix with players points diff
for (var y = 0; y < availablePlayers.length; y++) {
var playerY = availablePlayers[y]
for (var x = 0; x < availablePlayers.length; x++) {
var playerX = availablePlayers[x]
if (x === y) {
matrix[x][y] = maxDiff
}
else if (playerY.opponents.indexOf(x) !== -1) {
matrix[x][y] = maxDiff
matrix[y][x] = maxDiff
}
else {
var value = Math.abs(playerX.points - playerY.points)
matrix[x][y] = value
matrix[y][x] = value
}
}
}
// console.table(matrix)
// console.table(computeMunkres(matrix))
// return
// make pairing
var rawPairing = computeMunkres(matrix)
rawPairing.forEach(pairing => {
if (pairing[0] < pairing[1]) {
round.matches.push({
home: pairing[0],
home_score: '',
away: pairing[1],
away_score: '',
referee: -1
})
}
})

Related

Javascript Array - Remove duplicates based on another property

What is the most efficient method to dedupe a complex array based on the value of another property? I have found many examples that will dedupe an array or complex array but not this specific use case.
I am trying to find records with unique values in column 3 (state) with the highest number in column 2 (license)
var arrayWithDuplicates = [
["Boat", 1, "NV"],
["Car", 7, "CA"],
["Boat", 3, "NV"],
["Boat", 4, "CA"],
["Car", 5, "OR"],
["Boat", 6, "CA"],
];
Desired outcome
var outputArray = [
["Car", 7, "CA"],
["Boat", 3, "NV"],
["Car", 5, "OR"]
];
This works but not sure if with large datasets
var arrayWithDuplicates = [
["Boat", 1, "NV"],
["Car", 7, "CA"],
["Boat", 3, "NV"],
["Boat", 4, "CA"],
["Car", 5, "OR"],
["Boat", 6, "CA"],
];
let arr= arrayWithDuplicates;
let unique = []
for (let i = 0; i < arr.length; i++) {
let found = false;
for (let j = 0; j < unique.length; j++) {
if (arr[i][2] === unique[j][2]) {
found = true;
if (arr[i][1] > unique[j][1]) {
unique[j] = arr[i];
}
break;
}
}
if (!found) {
unique.push(arr[i])
}
}
console.log(unique);
[["Boat", 3, "NV"], ["Boat", 7, "CA"], ["Car", 5, "OR"]]
You can see the performance of the proposed solutions: https://jsbench.me/eskxxcwnhn/1
One way to solve your problem would be to use a Map instance to hold the most relevant value. Then replace it if you encounter another. Then, when you are done iterating, you take the values that are present in the Map instance.
const arrayWithDuplicates = [
["Boat", 1, "NV"],
["Car", 7, "CA"],
["Boat", 3, "NV"],
["Boat", 4, "CA"],
["Car", 5, "OR"],
["Boat", 6, "CA"],
];
const lookup = new Map();
for (const record of arrayWithDuplicates) {
const key = record[2];
if (!lookup.has(key)) {
lookup.set(key, record);
continue;
}
const other = lookup.get(key);
if (record[1] > other[1]) {
// Iteration order is based on insertion order. By removing the
// current value first, the new value will be placed at the end.
// If you don't care about the order, deletion can be omitted.
lookup.delete(key);
lookup.set(key, record);
}
}
const result = Array.from(lookup.values());
console.log(result);
Note that the following code sequence can be a fairly heavy operation:
lookup.delete(key);
lookup.set(key, record);
Due to the fact that it rearranges the iteration order of the Map contents. This is only done get the result in the order you are looking for. If the order of the resulting items is irrelevant, then you should remove the lookup.delete(key) call to improve the execution speed.
Although using Map instances might produce some execution overhead for small collections. The improved lookup speed really shines when collections get larger.
Using Sort and Reduce
const arrayWithDuplicates = [ ["Boat", 1, "NV"], ["Car", 7, "CA"], ["Boat", 3, "NV"], ["Boat", 4, "CA"], ["Car", 5, "OR"], ["Boat", 6, "CA"], ];
let deduped = [...arrayWithDuplicates]; // take a copy
deduped.sort((a, b) => { // sorts in place
if (a[0] < b[0]) return 1; // sort on names
if (a[0] > b[0]) return -1;
return b[1] - a[1]; // sort on second element
})
// reduce into an object keyed on state
deduped = Object.values( // take only the values from the object
deduped.reduce((acc, cur) => {
const state = cur[2];
if (!acc[state]) acc[state] = cur;
return acc;
},{}))
console.log(deduped)

How to find repeated values and store repeated values into new array in javascript

I am trying to implement a logic where I have an array [3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6] . I want to find all repeated elements and I want to store these all repeated elements into a new array. I tried very hard but didn't find a solution.
It would be great if someone write simple code and also explain what code doing .
Thanks
First of all you need to understand some basics.
Array Map - Information on MDN
Array map creates a new array and doesn't alter your current array.
//So if we do:
[3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6].map((element, index, array) => { return 0; } );
//We get
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Array.indexOf(element) Information on MDN
Returns the index of the element in an array, -1 not found, 0 based.
//So if we do:
[3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6].map( (element, index, array) => { return array.indexOf(element); } );
// we get
// [0, 1, 2, 3, 3, 5, 3, 7, 2, 7, 1, 3, 12, 7, 1, 15, 16, 7, 18, 5, 1, 3, 22, 0, 12]
//so if we do use the second parameter of map in combination with indexOf
[3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6].map( (element, index, array) => { return array.indexOf(element) === index; } );
// we get
// [true, true, true, true, false, true, false, true, false, false, false, false, true, false, false, true, true, false, true, false, false, false, true, false, false]
To wrap things up:
[3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6].map( (element, index, array) => {
if ( array.indexOf(element) !== index ) {
return element;
} else {
return undefined;
}
});
// will give us
// [undefined, undefined, undefined, undefined, 5, undefined, 5, undefined, 63, 2, 4, 5, undefined, 2, 4, undefined, undefined, 2, undefined, 1, 4, 5, undefined, 3, 6]
Array Filter Information on MDN
// so now we have our array of elements that are duplicated with undefined values in it, now we filter.
[undefined, undefined, undefined, undefined, 5, undefined, 5, undefined, 63, 2, 4, 5, undefined, 2, 4, undefined, undefined, 2, undefined, 1, 4, 5, undefined, 3, 6].filter( (element) => {
return element !== undefined;
});
//result
[5, 5, 63, 2, 4, 5, 2, 4, 2, 1, 4, 5, 3, 6]
This is the most basic explanation i can give, no use of conditional operators, storing in variables etc.. if you are a bit more advanced in programming you can write less shorter code, called 1 liners. But i hope this will give you to inspiration to go on with this. But you have to understand the basic first, that means learn, read, learn more and practice a lot.
If you need only repeative items of array you can use this:
var arry = [3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6];
var rep_arry = [];
for (const [key,val] of Object.entries(arry)) {
for (const [key1,val1] of Object.entries(arry)) {
if(val == val1 && !rep_arry.includes(val) && key != key1){
rep_arry.push(val);
}
}
}
console.log(rep_arry);
It returns this
[3, 4, 63, 5, 1, 2, 6]
Above code using two for loop which checks each individual elements with each using two for loops. before push I put 3 conditions, 1 for check equals, 2 for already exists check if item available into new array or not and 3rd one for prevent to check by its own element using keys. Its works large amount of items as well. see the snippet.
<html>
<script>
var arry = [3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6,3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6];
var rep_arry = [];
for (const [key,val] of Object.entries(arry)) {
for (const [key1,val1] of Object.entries(arry)) {
if(val == val1 && !rep_arry.includes(val) && key != key1){
rep_arry.push(val);
}
}
}
console.log(rep_arry);
</script>
</html>
We can solve this problem by keeping the list of unique elements.
If we will keep the list of unique elements, then it will make easy to collect duplicate elements.
This is maybe not best solution for efficiency, but it's solves the current problem. I commented the code so it maybe will help you to understand what is going on.
To solve this kind of problems, it's very preferable to learn about Algorithms.
const arr = [3,4,63,5,5,1,5,2,63,2,4,5,6,2,4,56,74,2,671,1,4,5,7,3,6];
let uniqueElmts = []; // here we gonna store unique elements
let duplicates = []; // here we gonna store duplicate elements
for(let i = 0; i < arr.length; i++){
if(uniqueElmts.includes(arr[i])){ // if 'uniqueElmts' already contains arr[i], then arr[i] is a duplicate
duplicates.push(arr[i]); // push arr[i] element to 'duplicates' array, because it's duplicate
}
else {
uniqueElmts.push(arr[i]); // if 'uniqueElmts' doesn't contain arr[i] then it's unique element so we push arr[i] to 'uniqueElmts'
}
}
console.log(duplicates); // [ 5, 5, 63, 2, 4, 5, 2, 4, 2, 1, 4, 5, 3, 6 ]

Find the average of each index in 2+ arrays to return a new array of averages

For this problem, what is the most ideal solution in JavaScript to take a bunch of arrays all with the same number of indexes which all have integer values and then return one array with the average of each index from each array.
Here is an example of what I mean:
var data = [[ 12, 14, 13, 10 ], [ 11, 13, 12, 2 ], [ 18, 12, 3, 4 ]];
to return one single array with all the averages calculated like so:
[13.6, 13, 9.3, 5.3 ];
data=data.map(arr=>arr.reduce((old,new)=>old+new,0)/arr.length);
I dont give an explanation, i give the OP the ability to find out alone + learn it that way...
You have to use map function in order to calculate average for every item from array.I'm using reduce function in order to calculate the sum for every item.
Here is solution:
var data = [[ 12, 14, 13, 10 ], [ 11, 13, 12, 2 ], [ 18, 12, 3, 4 ]];
console.log(data.map(function(item){
return item.reduce( ( prev, current ) => prev + current , 0 ) / item.length;
}));

The lodash way to delete nested array elements

I have an array like:
[[0,1,2,3][0,1,2,3,][0,1,2,3][0,1,2,3]]
I want to slice the nested arrays to keep only the first two elements.
I am using this code with lodash:
for (i = 0; i < data.length; ++i) {
data[i] = _.slice(data[i], [start=0], [end=2]);
}
This doesn't feel very lodash though. How would you approach it?
You can achieve it this way:
var data = [[0,1,2,3],[0,1,2,3,],[0,1,2,3],[0,1,2,3]];
_.invoke(data, 'slice', 0, 2);
Otherwise, if you want to use map:
data.map(function(item) { return item.slice(0, 2); }); // pure js solution
_.map(data, function(item) { return _.slice(item, 0, 2); } );
Here's what I would do:
var collection = [
[ 0, 1, 2, 3 ],
[ 0, 1, 2, 3 ],
[ 0, 1, 2, 3 ],
[ 0, 1, 2, 3 ]
];
_.map(collection, _.ary(_.partialRight(_.take, 2), 1));
// → [ [ 0, 1 ], [ 0, 1 ], [ 0, 1 ], [ 0, 1 ] ]
Here's what's going on:
The ary() function is making sure the callback only gets one argument passed to it, the collection item.
The partialRight() function is partially-applying 2 to the take() function as the second argument. The first argument will be the collection item.
The take() function is taking the first n items from the array.

Javascript Find the next occurrence of a "letter"

I just need an idea!
If I have a string "string1, values, string2, values, string3, values, ....."
How can I get the index of the second string? why? because I want divide this string into
"string1, values"
"string2, values"
...
by using substring(0, index-1 of the second string) and so on.
To be more clear I have
""a", 4, 2, 2, 4, 5, "b", 6, 4, 3, 6, 7, "x", 5, 6, 7, 8, .... "
I want the "second" occurrence of a letter
It's not entirely clear to me what you're looking for, maybe this?
str = '"a", 4, 2, 2, 4, 5, "b", 6 4 3 6 7, "x", 5 6 7 8"'
obj = {}, last = ""
str.replace(/(\d+)|(\w+)/g, function(_, d, w) {
d ? obj[last].push(parseInt(d)) : obj[last = w] = []
})
This populates an object obj like this:
{"a":[4,2,2,4,5],"b":[6,4,3,6,7],"x":[5,6,7,8]}

Categories