Im new here and into programming. Im not native english so please apologize if you dont understand me very well :-)
I`ve started learning Javascript and still doing.
Now I want to achieve a simple project which gives me two Teams with equal skills.
Questions:
It seems to work but I want to get random teams. This gives me always the same. How can I resolve this?
I am able to print the values to see that its correct, but how can I print the player names (Variables like P1, P2, P3...)
Thank you
//Player skill values
let P1 = 2;
let P2 = 3;
let P3 = 1;
let P4 = 3;
let P5 = 4;
let P6 = 4;
let P7 = 5;
let P8 = 2;
//try to achieve two equal teams
function aufteilen(arr) {
arr.sort().reverse()
let a = [], b = [], sumA = 0, sumB = 0, i = 0
while (i < arr.length) {
if (!sumA && !sumB || sumA == sumB) {
a.push(arr[i])
sumA += arr[i]
} else if (sumA < sumB) {
a.push(arr[i])
sumA += arr[i];
} else if (sumB < sumA) {
b.push(arr[i])
sumB += arr[i];
}
i++
}
console.log(`Total: ${sumA} ${sumB}`)
return [a, b]
}
console.log(aufteilen([P1, P2, P3, P4, P5, P6, P7, P8]))
Creating a team selection based on skill and making it random using the OP's algorithm is not really possible.
It's also possible for the OP's code to make teams based on player count not equal, I would assume a team picker would try to make teams equal on players & skill.
So one approach is just a simple brute force one, basically randomly pick teams, and compare the skills. Stop comparing after a set amount of time, just in case a perfectly equal skill cannot be met, or when skills are equal.
Below is a working example doing the above, it will also handle making the teams equal if we don't have an even number of players.
const players = [
{name: 'P1', skill: 2},
{name: 'P2', skill: 3},
{name: 'P3', skill: 1},
{name: 'P4', skill: 3},
{name: 'P5', skill: 4},
{name: 'P6', skill: 4},
{name: 'P7', skill: 5},
{name: 'P8', skill: 2},
];
const score = team =>
team.reduce((a, v) => {
a += v.skill;
return a;
}, 0);
function createFairTeam(players) {
function createTeams() {
const team = {
team1: [],
team2: [],
skillTot1: 0,
skillTot2: 0,
score: 0
}
const p = [...players];
const t1size = Math.trunc(players.length / 2);
//fill team1
for (let l = 0; l < t1size; l++) {
const pos = Math.trunc(Math.random() * p.length);
const f = p.splice(pos, 1)[0];
team.skillTot1 += f.skill;
team.team1.push(f);
}
//fill team2
for (let l = 0; l < p.length;) {
const pos = Math.trunc(Math.random() * p.length);
const f = p.splice(pos, 1)[0];
team.team2.push(f);
team.skillTot2 += f.skill;
}
//calc score.
team.score = Math.abs(
score(team.team1) -
score(team.team2)
);
return team;
}
const team = createTeams();
let best = team;
const start_time = Date.now();
while (Date.now() - start_time < 200) {
//if total skill is odd, the best score
//would be 1, if even it would be 0
//so lets check for less than equal to 1
//for early termination.
if (best.score <= 1) break;
const newTeams = createTeams();
if (newTeams.score < best.score)
best = newTeams;
}
return best;
}
function doIt() {
const teams = createFairTeam(players);
const table = document.querySelector('table');
const tbody = table.querySelector('tbody');
tbody.innerHTML = '';
for (let l = 0;
l < Math.max(teams.team1.length, teams.team2.length); l ++) {
const p1 = teams.team1[l];
const p2 = teams.team2[l];
const tr = document.createElement('tr');
////
let td = document.createElement('td');
td.innerText = p1 ? p1.name : '';
tr.appendChild(td);
////
td = document.createElement('td');
td.innerText = p1 ? p1.skill : '';
tr.appendChild(td);
////
td = document.createElement('td');
td.innerText = p2 ? p2.name : '';
tr.appendChild(td);
tbody.appendChild(tr);
////
td = document.createElement('td');
td.innerText = p2 ? p2.skill : '';
tr.appendChild(td);
tbody.appendChild(tr);
}
//// tots
document.querySelector('#t1total').innerText = teams.skillTot1;
document.querySelector('#t2total').innerText =
teams.skillTot2;
}
doIt();
document.querySelector('button').addEventListener('click', doIt);
table {
width: 100%;
}
<button>Another Team</button>
<table border="1">
<thead>
<tr>
<th colspan="2">Team 1</th>
<th colspan="2">Team 2</th>
</tr>
<tr>
<th>Player</th>
<th>Skill</th>
<th>Player</th>
<th>Skill</th>
</tr>
</thead>
<tbody></tbody>
<tfoot>
<tr>
<td>Total</td><td id="t1total"></td>
<td>Total</td><td id="t2total"></td>
</tr>
</tfoot>
</table>
1 - you need a shuffle method, something like below or from a library:
function shuffle(array) {
array.sort(() => Math.random() - 0.5);
}
2 - you need an object to display player names. your players are just numbers.
// your player
let P1 = 2;
// what you would need to display a name
let P1 = { id: 2, name: "Mario" }
// then u can display the name with
P1.name // "Mario"
Your array.sort().reverse() is the reason why you get this.
I would recommend to shuffle() before passing it to your function.
Here is my guess :
function shuffle(arr) {
for (let i = arr.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
console.log(aufteilen(shuffle([P1, P2, P3, P4, P5, P6, P7, P8])));
You can then create an object Players, add all infos you want and do it like this :
let players = {
P1: 2,
P2: 3,
P3: 1,
P4: 3,
P5: 4,
P6: 4,
P7: 5,
P8: 2,
};
let playerSkills = Object.values(players);
console.log(aufteilen(shuffle(playerSkills)));
Hope it will help !
Related
I have an array with names
const players = ['name1','name2','name3','name4','name5','name6','name7','name8'];
const teams = players.length / 2; // -> 4 teams
I want to make teams of 2 people (randomly chosen) and make new arrays from the results -> team
function createTeams() {
for (let i = 0; i < teams; i++) {
for (let x = 0; x < 2; x++) {
// get a random player
selectedPlayer = players[Math.floor(Math.random() * players.length)];
console.log('Selected Player will be added to a team: ', selectedPlayer);
// delete this player from the array
while (players.indexOf(selectedPlayer) !== -1) {
players.splice(players.indexOf(selectedPlayer), 1);
}
// add this player to a new array
//?????
}
}
}
Anyone knows how to do this?
function pickTeams(array, teamSize) {
// Shuffle array, make a copy to not alter original our original array
const shuffled = [...array].sort( () => 0.5 - Math.random());
const teams = [];
// Create teams according to a team size
for (let i = 0; i < shuffled.length; i += teamSize) {
const chunk = shuffled.slice(i, i + teamSize);
teams.push(chunk);
}
return teams;
}
const players = ['name1','name2','name3','name4','name5','name6','name7','name8'];
const teams = pickTeams(players, 2);
You can define a new Array which will contains the teams where you push the two players.
Note that it's better to pass the players array as a parameters of the function and make a swallow copy of it so it won't modify the player array.
const players = ['name1','name2','name3','name4','name5','name6','name7','name8'];
function createTeams(players) {
const playersLeft = [...players]
const newTeams = []
const nbTeams = players.length / 2
for (let i=0; i<nbTeams; i++){
const player1 = playersLeft.splice(Math.floor(Math.random()*playersLeft.length),1)[0]
const player2 = playersLeft.splice(Math.floor(Math.random()*playersLeft.length),1)[0]
newTeams.push([player1, player2])
}
return newTeams
}
console.log(createTeams(players))
Edit : Improve version using a nbPlayerPerTeam parameter
const players = ['name1', 'name2', 'name3', 'name4', 'name5', 'name6', 'name7', 'name8'];
function createTeams(players, nbPlayerPerTeam) {
const playersLeft = [...players]
const newTeams = []
const nbTeams = players.length / nbPlayerPerTeam
for (let i = 0; i < nbTeams; i++) {
const players = []
for (let j = 0; j < nbPlayerPerTeam; j++) {
const player = playersLeft.splice(Math.floor(Math.random() * playersLeft.length), 1)[0]
players.push(player)
}
newTeams.push(players)
}
return newTeams
}
console.log(createTeams(players, 3))
const players = ['name1', 'name2', 'name3', 'name4', 'name5', 'name6', 'name7', 'name8'];
// Divide data by length
const divide = (arr, n) => arr.reduce((r, e, i) =>
(i % n ? r[r.length - 1].push(e) : r.push([e])) && r, []);
const shuffle = (arr) => {
// Deep copy an array using structuredClone
const array = structuredClone(arr);
let currentIndex = array.length, randomIndex;
// While there remain elements to shuffle
while (currentIndex != 0) {
// Pick a remaining element
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// And swap it with the current element
[array[currentIndex], array[randomIndex]] = [
array[randomIndex], array[currentIndex]
];
}
return array;
}
// Shuffle players
const shuffled = shuffle(players);
// Set a number of people in a team
const groups = divide(shuffled, players.length / 2);
groups.forEach((team, i) => console.log(`team ${i+1}`, JSON.stringify(team)));
Check this updated code, I hope it will work for you. Just create two dimensional array and push players.
const players = ['name1','name2','name3','name4','name5','name6','name7','name8'];
let multi_team=new Array; // new array for team of random 2 players
const teams = players.length / 2; // -> 4 teams
console.log(teams);
createTeams();
function createTeams() {
for (let i = 0; i < teams; i++) {
multi_team[i]= new Array;
for (let x = 0; x < 2; x++) {
// get a random player
selectedPlayer = players[Math.floor(Math.random() * players.length)];
multi_team[i][x]=selectedPlayer;
// delete this player from the array
while (players.indexOf(selectedPlayer) !== -1) {
players.splice(players.indexOf(selectedPlayer), 1);
}
}
}
console.log(multi_team);
}
To differ from previous answers, this is a dynamic way to do it, so you don't care if there are 5 teams or 2, 10 players or 500.
const players = ['j1', 'j2', 'j3', 'j4', 'j5', 'j6', 'j7', 'j8'];
const organizeTeams = (numberOfTeams, players) => {
var teams = [];
// Clone the array to avoid mutations
const freePlayers = [...players];
// How many players will play per team
const playersPerTeam = Math.floor(players.length / numberOfTeams);
// How many player won't have team
const unorganizablePlayers = freePlayers.length % numberOfTeams;
for (let t = 1; t <= numberOfTeams; t++) {
teams = [...teams, pickRandomPlayers(freePlayers, playersPerTeam)];
}
return { teams, playersPerTeam, unorganizablePlayers };
};
const pickRandomPlayers = (players, playersPerTeam) => {
let pickedPlayers = [];
for (let c = 1; c <= playersPerTeam; c++) {
const index = Math.floor(Math.random() * (players.length - 1));
const player = players[index];
pickedPlayers = [...pickedPlayers, player];
// When we pick the player we remove it from the array to avoid duplicates.
players.splice(index, 1);
}
return pickedPlayers;
};
const championship = organizeTeams(3, players);
console.log(`We have ${championship.teams.length} teams.`);
championship.teams.forEach((team, index) => {
console.log(`Team ${index + 1} players: ${team.join(',')}`);
});
console.log(
`There was no place to assign ${championship.unorganizablePlayers} players`
);
The OP needs (to implement ) a reliable shuffle and likewise an array's chunk functionality.
Then one just has to shuffle the provided players array (or a shallow copy of the latter) and to divide the array of randomly ordered player items into chunks, each of custom provided length.
function shuffleArray(arr) {
let idx;
let count = arr.length;
while (--count) {
idx = Math.floor(Math.random() * (count + 1));
[arr[idx], arr[count]] = [arr[count], arr[idx]];
}
return arr;
}
function createListOfChunkLists(arr, chunkLength = 0) {
chunkLength = Math.max(0, chunkLength);
return (chunkLength === 0 && []) ||
arr.reduce((list, item, idx) => {
if (idx % chunkLength === 0) {
list.push([ item ]);
} else {
list[list.length - 1].push(item);
// `at` still not supported by safari.
// list.at(-1).push(item);
}
return list;
}, []);
}
// ... OP ... I have an array with names
const players = ['name1', 'name2', 'name3', 'name4', 'name5', 'name6', 'name7', 'name8'];
// ... OP ... I want to make teams of 2 people (randomly chosen)
// and make new arrays from the results -> team
const teams = createListOfChunkLists(
shuffleArray([...players]), 2
);
console.log({ teams, players });
console.log({
teams: createListOfChunkLists(
shuffleArray([...players]), 2
),
players
});
console.log({
teams: createListOfChunkLists(
shuffleArray([...players]), 4
),
players
});
.as-console-wrapper { min-height: 100%!important; top: 0; }
I would like to enter 3 players with 3 scores in using 2 arrays.
For now I stuck, concerning the ranking; how to do ???
Small example:
Player 1 : Jeremy
Score Jeremy : 12
Player 2 : Julien
Score Julien : 18
Player 3 : Olivia
Score Olivia : 22
For the ranking we should have
The first => Olivia with 22 scores
The second => Julien with 18 scores
The third => Jeremy with 12 scores
Here is my code.
function main() {
var players = new Array();
var scores = new Array();
for(i = 0; i<3; i++) {
players[i] = prompt("Player " + (i+1) + " : ");
scores[i] = prompt("Score " + (players[i]) + " : ");
}
}
Thank you advance.
Splitting data into two arrays is probably not the easiest way to go, but assuming you have no choice, I would use a custom sorting function and an additional array containing indices related to the other arrays :
main();
function main() {
var players = new Array();
var scores = new Array();
var ranking = new Array();
for (var i = 0; i < 3; i++) {
ranking[i] = i;
players[i] = prompt("Player " + (i + 1) + " : ");
scores[i] = prompt("Score " + (players[i]) + " : ");
}
indirectSort(3, ranking, scores);
for (var i = 0; i < 3; i++) {
var ithPlayer = players[ranking[i]];
var ithScore = scores[ranking[i]];
console.log(ithPlayer, ithScore);
}
}
function indirectSort (n, toBeSorted, toBeCompared) {
for (var i = 0; i < n - 1; i++) {
for (var j = i + 1; j < n; j++) {
var a = toBeCompared[toBeSorted[j]];
var b = toBeCompared[toBeSorted[i]];
if (a < b) {
var min = toBeSorted[j];
toBeSorted[j] = toBeSorted[i];
toBeSorted[i] = min;
}
}
}
}
Here is how the above code works :
players = ["John", "Jack", "Bob"];
scores = [2, 1, 3];
ranking = [0, 1, 2];
indirectSort(3, ranking, scores);
console.log(ranking);
// [1, 0, 2]
console.log(players[ranking[0]]);
// "Jack"
console.log(scores[ranking[0]]);
// 1
This will give you a sorted Players array, where each player has a name and a score
function main()
{
var players = new Array();
for(i = 0; i<3; i++){
let player = {};
player.name = prompt("Player " + (i+1) + " : ");
player.score = prompt("Score " + (players[i]) + " : ");
players[i] = player;
}
players.sort(function(a,b){
if (a.score < b.score)
return -1;
if (a.score> b.score)
return 1;
return 0;
})
console.log(players);
}
You could and should be storing each user as an object in an array like this:
function main(){
var players = [];
for(var i = 0;i<3;i++){
var player = {};
player.name = prompt("Enter user name");
var ranking = prompt("Enter user ranking");
player.ranking = parseInt(ranking);
players.push(player)
}
console.log("Players");
players.forEach(function(player){
console.log(player.name,player.ranking);
});
//Now sort based on ranking
players = players.sort(function(pA,pB){
return pA.ranking - pB.ranking;
});
players.forEach(function(player){
console.log(player.name,player.ranking);
});
}
main();
Try following. I am just using push of array(for adding an object to array). And for sorting you can use sort(for sorting array based on the score) of Array.Prototype. See below code.
var array=[];
function add(){
var x={};
x.name=prompt("Player " + (array.length+1) + " : ");
x.score=parseInt(prompt("Score " + (x.name) + " : "));
array.push(x);
array.sort((a,b)=>a.score<b.score)
console.log(array);
}
<button onclick="add()">add</button>
You should use array of objects instead of separate arrays like:
let data = [
{name: 'Jeremy', score: 12},
{name: 'Julien', score: 18},
{name: 'Olivia', score: 22}
];
You can then sort and print result with .sort():
let data = [
{name: 'Jeremy', score: 12},
{name: 'Julien', score: 18},
{name: 'Olivia', score: 22}
];
data.sort((a, b) => b.score - a.score);
console.log(`The first => ${data[0].name} with ${data[0].score} scores`);
console.log(`The second => ${data[1].name} with ${data[1].score} scores`);
console.log(`The third => ${data[2].name} with ${data[2].score} scores`);
However if for some reasons you want to stick with 2 arrays, you can also transform both arrays in single array of object and print result.
let players = ['Jeremy', 'Julien', 'Olivia'];
let score = [12, 18, 22];
function printResult(p, s) {
let a = p.reduce((a, c, i) => (a.push({name: c, score: s[i]}), a), [])
.sort((a, b) => b.score - a.score);
console.log(`The first => ${a[0].name} with ${a[0].score} scores`);
console.log(`The second => ${a[1].name} with ${a[1].score} scores`);
console.log(`The third => ${a[2].name} with ${a[2].score} scores`);
}
printResult(players, score);
Useful Resources:
Array.prototype.sort()
Template literals
Yesterday, I faced this interview question. Initially, it seemed pretty easy, at least logically.
But somehow, I could not make it work in JavaScript.
Here is a 2d array of student scores, student names might be repeated multiple times. If this is the case, sum up all the scores and divide by the number of occurrences to find the average, do the Math.floor if necessary.
var arr = [
["Bobby","87"],
["Charles","100"],
["Eric","65"],
["Charles","22"],
["Charles","37"],
["Eric","49"]]
So, Charles average score would be Math.floor((100+22+37)/3) = 53
And, for Eric it would be Math.floor((65+49)/2) = 57.
So highest average would be ["Bobby","87"].
So far what I have tried fruitlessly..
var op_arr = [];
arr.each(function(item) {
var sum = 0;
var itemCount = 1;
var checkFlag = isItemInArray(arr,item);
if(checkFlag) {
itemCount++;
sum += item[1];
}
});
function isItemInArray(array,item) {
for(let i = 0;i < array.length; i++) {
if(array[i][0] === item[0]) {
return array[i];
}
}
return false;
}
But this does not work.
Please help me and explain the logic.
I would use some code to convert the list into a hash map based on the fact that there can be multiple of the same student.
var arr = [
["Bobby","87"],
["Charles","100"],
["Eric","65"],
["Charles","22"],
["Charles","37"],
["Eric","49"]
];
var scores = {};
for (var i = 0; i < arr.length; i++) {
var student = arr[i];
if (!scores.hasOwnProperty(student[0]))
scores[student[0]] = []
scores[student[0]].push(student[1])
}
The result should be:
{
"Bobby": ["87"],
"Charles": ["100", "22", "37"],
"Eric": ["65", "49"]
}
And now you can do a second pass over the object to calculate the average
for (var key in scores) {
if (!scores.hasOwnProperty(key)) continue;
var total = scores[key].reduce(function(next, cur) {
return next + parseInt(cur);
}, 0);
scores[key] = Math.floor(total / scores[key].length);
}
console.log(scores);
I'm sure you can make it a lot more elegant using ES6 features, but this should give you an idea of one solution.
You could first get all average values of each person and then get the highest average of the temporary result.
If more than one person have the same score, all persons are included.
var array = [["Bobby", "87"], ["Charles", "100"], ["Eric", "65"], ["Charles", "22"], ["Charles", "37"], ["Eric", "49"]],
highest = array
.reduce(function (r, a) {
var i = r.findIndex(b => a[0] === b[0]);
if (i !== -1) {
r[i][1] = (r[i][1] * r[i][2] + +a[1]) / ++r[i][2];
} else {
r.push(a.concat(1));
}
return r;
}, [])
.reduce(function (r, a, i) {
if (!i || r[0][1] < a[1]) {
return [a.slice(0, 2)];
}
if (r[0][1] === a[1]) {
r.push(a.slice(0, 2));
}
return r;
}, []);
console.log(highest);
You can following this approach
Collate all the scores for a name in an array of scores using reduce
Iterate the result using map and compute the average of scores array using reduce again.
Demo
var fnSumAvgArray = (arr) => arr.reduce( ( a, c ) => a + c, 0 )/arr.length;
var arr = [
["Bobby","87"],
["Charles","100"],
["Eric","65"],
["Charles","22"],
["Charles","37"],
["Eric","49"]];
var output = Object.values( arr.reduce( (a,c) => (
a[c[0]] = (a[c[0]] || { name : c[0], scores : [] }), //check if accumulator already has been initialized for this name or else intialize
a[ c[ 0 ] ].scores.push ( +c[1] ), //push the score into the name based score array
a ) , {}) ) //return the accumulator
.map( s => (
s.avg = fnSumAvgArray(s.scores), s
));
console.log( output );
var arr = [
["Bobby","87"],
["Charles","100"],
["Eric","65"],
["Charles","22"],
["Charles","37"],
["Eric","49"]];
var x, students=[], counter=[], scoreSums=[], name, i=0;
for(x in arr) {
name = arr[x][0];
j = students.indexOf(name);
if(j < 0) {
students[i] = name;
counter[i] = 1;
scoreSums[i] = parseInt(arr[x][1]);
i++;
} else {
scoreSums[j] += parseInt(arr[x][1]);
counter[j]++;
}
}
var maxMean = 0, mean;
for(i=0; i<students.length; i++) {
mean = scoreSums[i] / counter[i];
if(mean > maxMean) {
name = students[i];
maxMean = mean;
}
}
document.getElementById('maxMean').innerHTML='Highest average is '+ name+' at '+maxMean;
console.log(name, maxMean);
<div id="maxMean"></div>
Here is a 'round the houses' approach. It is a slightly longer way to write it out but I think it's human readable which should help with the concept.
I've commented the code below.
var arr = [
["Bobby", "87"],
["Charles", "100"],
["Eric", "65"],
["Charles", "22"],
["Charles", "37"],
["Eric", "49"]
];
let objects = [];
let names = [];
for (let item of arr) {
// Get unique names
if (names.indexOf(item[0]) <= 0) {
names.push(item[0]);
}
// Create object rather than array
let score = {
name: item[0],
score: parseInt(item[1])
}
objects.push(score);
}
// Work out average based on name
function countScore(name) {
let count = 0;
let total = 0;
for (let object of objects) {
if (object.name === name) {
count += 1;
total += object.score
}
}
let avgScore = total / count;
console.log(name + ': ' + avgScore)
}
// Run function for each unique name
for (let name of names) {
countScore(name);
}
I hope you find this helpful.
let findMaxAvg = (arr) => {
let studmap = new Map();
arr.forEach((item,ind) => {
let [name,score] = [item[0],item[1]];
!studmap.has(name) ? studmap.set(name,[score,1]) : studmap.set(name,
[studmap.get(name)[0]+score,studmap.get(name)[1]+1])
});
return Math.max(...[...studmap.values()].map(sdata =>
Math.round(sdata[0]/sdata[1])));
}
var arr = [
["Bobby","87"],
["Charles","100"],
["Eric","200"],
["Charles","22"],
["Charles","37"],
["Eric","49"]];
console.log(findMaxAvg(arr));
There's a lot of ways you can solve this as per the solutions provided above. I've also encountered this question before and I used
Map()
to collect all students and their scores and return array with [name, average].
const scores = [['brian', 80], ['brian', 90], ['brian', 65], ['robhy', 85], ['robhy', 75], ['ryan', 75], ['murphy', 85]];
const calculateHigestAvg = (scores)=>{
let studentObj = new Map();
let score = [];
for (var i = 0; i < scores.length; i++) {
if (studentObj.has(scores[i][0])) {
const studentScoreArr = studentObj.get(scores[i][0])
studentScoreArr.push(scores[i][1]);
studentObj.set(scores[i][0], studentScoreArr);
} else {
studentObj.set(scores[i][0], [scores[i][1]]);
}
}
return Array.from(studentObj.entries())
.map((item, index)=>{
return [item[0], Math.floor(item[1].reduce((a, b) => a + b) / item[1].length)]
}
, 0)
}
calculateHigestAvg(scores);
// results
[["brian", 78],["robhy", 80],["ryan", 75],["murphy", 85]]
I wish to sort an array of medals. My first sort returns an array sorted according to the gold medals. I then wish to range those which are having the same gold but silver medals are different (same for bronze). I use the following codes that actually makes me run out of memory. This is my code:
static sort(data) {
let sorted = data.sort((a, b) => b.medal.gold - a.medal.gold);
let next, temp, current;
for (let i = 0; i < sorted.length; i++) {
current = sorted[i].medal;
if (sorted[i+1]) next = sorted[i+1].medal;
if (next) {
if (current.gold === next.gold) {
if (current.silver < next.silver) {
temp = sorted[i+1];
sorted[i+1] = sorted[i];
sorted[i] = temp;
}
else if (current.silver === next.silver) {
if (current.bronze < next.bronze) {
temp = sorted[i+1];
sorted[i+1] = sorted[i];
sorted[i] = temp;
}
}
}
}
}
return sorted;
}
You'll want to improve your compare function so it takes care of that requirement:
data.sort((a, b) => (b.medal.gold - a.medal.gold)
|| (b.medal.silver - a.medal.silver)
|| (b.medal.bronze - a.medal.bronze) )
And then you don't need the (endless) for loop at all.
You have to set next to null somewhere, because it keeps the value from the previous iteration and the if(next) is always true. Afterwards the function will always create one more element and add it in the array (sorted[i+1] = sorted[i]) until you run out of memory.
Here is a working example:
var rawData =
[{ id: 1, medal: {gold: 2, silver: 1, bronze: 1}},
{ id: 2, medal: {gold: 2, silver: 1, bronze: 2} },
{ id: 3, medal: {gold: 5, silver: 1, bronze: 4} } ];
function sortData(data) {
let sorted = data.sort((a, b) => b.medal.gold - a.medal.gold);
let next, temp, current;
for (let i = 0; i < sorted.length; i++) {
next = undefined;
current = sorted[i].medal;
if (sorted[i+1]) next = sorted[i+1].medal;
if (next) {
if (current.gold === next.gold) {
if (current.silver < next.silver) {
temp = sorted[i+1];
sorted[i+1] = sorted[i];
sorted[i] = temp;
}
else if (current.silver === next.silver) {
if (current.bronze < next.bronze) {
temp = sorted[i+1];
sorted[i+1] = sorted[i];
sorted[i] = temp;
}
}
}
}
}
return sorted;
};
console.log(sortData(rawData))
Please note that in the function you are using medal instead of medals as the data you have provided in one of your comments.
This seems sort of complex so I'll do my best to be as clear as possible.The particular function I'm looking for dynamically creates a money spent | money won chart for a game of gambling.
I have a lottery of sorts that the user can bet on. There are 6 items the user can buy which each have 6 prizes:
These can be put into objects or arrays.
var prices = [5,10,28,50,56,280].
var possibleWins = [40,80,250,400,500,2500]
I'm trying to create a chart that calculates how much money you would have to spend on each particular item per game to guarantee you gain money - for 300 games out.
So here is an example of how the chart should start off:
investment = max possible winnings + total spent( which is negative )
The 2nd row is assuming the first game already happened and lost. And so on.
The idea is to start with the smallest item but give up once it can no longer get you positive even if you win. This is why on row 9 we switch to the rock. ( our investment is at 0 and if we play a twig again, the most we can win is 40. So even if we did win, we would actually have lost 5 overall. )
Also worth pointing out is that if you win on 1 item; you win on all items for that particular game. So you get all prizes combined.
I've been working on this for a few days now and some of these related questions have my initial attempts ( but I honestly have no idea ):
How to find the lowest possible combination of keys within an array
Counter that generates the lowest sum from a combination of indexes above the previous value
Add an arrays keys to themselves until exceeding a limit?
EDIT: At least 1 item(s) must be bought every game and games cannot be skipped
Basically this proposal relies on a function to get the next items
getItems = function () {
var price = 0,
array = lottery.map(function (a) { return a.price; });
return function () {
var items;
do {
items = combine(array, price);
price++;
} while (!items.length)
return items;
}
}(),
which starts at price with zero and increments the value by one until a combination of items is found. Then the items array is returned. The function works as generator.
The other important function is the combination of items with a given price and the try to get an array with the items.
function combine(array, sum) {
function c(left, right, sum) {
if (!sum) {
result = right;
return true;
}
return left.some(function (a, i, aa) {
return a <= sum && c(aa.slice(i + (a > sum - a)), right.concat(a), sum - a);
});
}
var result = [];
c(array.sort(function (a, b) { return b - a; }), [], sum);
return result;
}
combine takes an array with prices and a wanted sum to reach with combinating the given prices. If successfull, an array with the items is returned, otherwise an empty array.
The third part is to use the items as long as the investment is not negative. If that happens, a new items set is fetched.
function combine(array, sum) {
function c(left, right, sum) {
if (!sum) {
result = right;
return true;
}
return left.some(function (a, i, aa) {
return a <= sum && c(aa.slice(i + (a > sum - a)), right.concat(a), sum - a);
});
}
var result = [];
c(array.sort(function (a, b) { return b - a; }), [], sum);
return result;
}
var lottery = [{ name: 'twig', price: 5, win: 40 }, { name: 'rock', price: 10, win: 80 }, { name: 'shell', price: 28, win: 250 }, { name: 'chip', price: 50, win: 400 }, { name: 'gold', price: 56, win: 500 }, { name: 'diamond', price: 280, win: 2500 }],
lotteryByPrice = lottery.reduce(function (r, a) { r[a.price] = a; return r; }, Object.create(null)),
getItems = function () {
var price = 0,
array = lottery.map(function (a) { return a.price; });
return function () {
var temp;
do {
temp = combine(array, price);
price++;
} while (!temp.length)
return temp;
}
}(),
createTableRow = function (element) {
var table = document.createElement('table'),
tr = document.createElement('tr');
['Game', 'Items', 'Types', 'Spend Per Game', 'Total Spend', 'Max. Possible Winnigs', 'Investment'].forEach(function (a) {
var th = document.createElement('th');
th.appendChild(document.createTextNode(a));
tr.appendChild(th);
});
table.appendChild(tr);
element.appendChild(table);
return function (row) {
var tr = document.createElement('tr');
['game', 'items', 'types', 'spend', 'total', 'potential', 'investment'].forEach(function (k) {
var td = document.createElement('td');
td.appendChild(document.createTextNode(row[k]));
tr.appendChild(td);
});
if (row.topBorder) {
tr.style.borderTop = '2px solid #666';
}
table.appendChild(tr);
};
}(document.body),
row = { game: null, items: null, types: null, spend: null, total: 0, potential: null, investment: null },
i,
items = getItems(),
add = function (a, b) { return a + b; },
winP = function (a) { return lotteryByPrice[a].win; },
nameP = function (a) { return lotteryByPrice[a].name; };
for (i = 1; i <= 70; i++) {
row.topBorder = false;
while (row.total - items.reduce(add) + items.map(winP).reduce(add) < 0) {
items = getItems();
row.topBorder = true;
}
row.game = i;
row.items = items.length;
row.types = items.map(nameP).join(' + ');
row.spend = -items.reduce(add);
row.total += row.spend;
row.potential = items.map(winP).reduce(add);
row.investment = row.potential + row.total;
createTableRow(row);
}
table { border-collapse: collapse; font-family: Sans-Serif; }
th { border: 1px solid #ccc; padding: 0 10px; }
td { text-align: center; border: 1px solid #ccc; }
Here is my solution
let items = [{
name: 'twig',
price: 5,
win: 40
}, {
name: 'rock',
price: 10,
win: 80
}, {
name: 'shell',
price: 28,
win: 250
}, {
name: 'chip',
price: 50,
win: 400
}, {
name: 'gold',
price: 56,
win: 500
}, {
name: 'diamond',
price: 280,
win: 2500
}];
let moves = [];
Move.prototype.numberItems = function() {
let count = 0;
for (let n = 0; n < 6; n++) {
count += this.counts[n];
}
return count;
}
Move.prototype.nameItems = function() {
let name = '';
for (let n = 0; n < 6; n++) {
for (let x = 0; x < this.counts[n]; x++) {
if (name != '') {
name += ' - ';
}
name += items[n].name;
}
}
return name;
}
Move.prototype.getWin = function() {
let win = 0;
for (let n = 0; n < 6; n++) {
win += this.counts[n] * items[n].win;
}
return win;
}
function Move(cost, counts) {
this.cost = cost;
this.counts = counts.slice();
}
function run() {
createMoves(100);
moves.sort(function(a, b) {
return (a.cost - b.cost);
});
print();
}
function createMoves(maxCost) {
let counts = [];
for (let n = 0; n < 6; n++) {
counts.push(0);
}
counts[0] ++;
while (true) {
let cost = whatCost(counts);
if (cost < maxCost) {
moves.push(new Move(cost, counts));
counts[0] ++;
continue;
}
if (!escalate(counts)) {
break;
}
}
}
function whatCost(counts) {
let cost = 0;
for (let n = 0; n < 6; n++) {
cost += counts[n] * items[n].price;
}
return cost;
}
function escalate(counts) {
for (let n = 0; n < 5; n++) {
if (counts[n] != 0) {
counts[n] = 0;
counts[n + 1] ++;
return true;
}
}
return false;
}
function print() {
let domResult = document.getElementById('results');
let game = 1;
let moveInx = 0;
let spent = 0;
for (let moveInx = 0; moveInx < moves.length; moveInx++) {
let myMove = moves[moveInx];
let items = myMove.numberItems();
let win = myMove.getWin();
let cost = myMove.cost;
for (let repeat = 1;; repeat++) {
let investment = win - spent - cost;
if (investment < 0) {
break;
}
spent += cost;
let row = document.createElement('tr');
if (repeat == 1) {
row.className = 'first';
}
let cell = document.createElement('td');
cell.innerHTML = game;
row.appendChild(cell);
cell = document.createElement('td');
cell.innerHTML = items;
row.appendChild(cell);
cell = document.createElement('td');
cell.innerHTML = myMove.nameItems();
row.appendChild(cell);
cell = document.createElement('td');
cell.innerHTML = cost;
row.appendChild(cell);
cell = document.createElement('td');
cell.innerHTML = spent;
row.appendChild(cell);
cell = document.createElement('td');
cell.innerHTML = win;
row.appendChild(cell);
cell = document.createElement('td');
cell.innerHTML = win - spent;
row.appendChild(cell);
domResult.appendChild(row);
game++;
if (game > 300) {
return;
}
}
}
}
table {
border-collapse: collapse;
}
tr * {
border: solid 1px black;
}
.first {
border-top: solid 4px blue;
}
<button onclick="run()">Run</button>
<table>
<thead>
<tr>
<th>Game</th>
<th>Items</th>
<th>Types</th>
<th>Spent</th>
<th>Total Spent</th>
<th>Max win</th>
<th>Profit</th>
</tr>
</thead>
<tbody id="results">
</tbody>
</table>
You can create an object where property names are set the values of possibleWins. Set all of the possible combinations of investing the limit at each round. The arrays do not contain all possible combinations of numbers less than the limit for that particular round. That is, the numbers are not dispersed in every possible combination. For example, at round 40, [10, 10, 10, 10, 0, 0, 0, 0] is included as an array; though the array could also be rearranged to [10, 0, 10, 10, 0, 10, 0, 10], or other combination of indexes totaling less than 40.
Additional of the possible allowed combinations less than the limit for that round be pushed to the array corresponding a specific round at the returned object.
This implementation does not attempt to locate the selection routes of each round which would lead to a positive outcome. The entire set of arrays can be iterated as to each matching index in each array, combination of random indexes, or every possible combination of indexes.
The approach is a base template from which possible selections can be made. Further optional arrays containing combinations of values less object property name, that is the particular round, or values within arrays from within arrays at properties of object having a property name value less than the current round, can be added to the array of arrays; to find the combinations of selections which lead to the expected outcome.
const [prices, possibleWins] = [
[5, 10, 28, 50, 56, 280],
[40, 80, 250, 400, 500, 2500]
];
const counteropts = (prices, possibleWins) => {
let rounds = {};
for (let price of prices) {
let [chance, limit] = [[], possibleWins[prices.indexOf(price)]];
for (let buyin = price - price; buyin <= limit; buyin += price) {
chance[chance.length] = buyin;
}
if (chance[chance.length - 1] !== limit) {
chance = [...chance, limit]
}
for (let odd of Array.of(chance)) {
let options = Array();
for (let choice of odd) {
options[options.length] = [...odd.map(
v => v !== choice && v + choice <= limit ? v + choice : 0
)];
if (options.length === prices.length -1) {
for (let option of options[0]) {
let keys = options[0].map((_, index) => index + 1)
.filter(key => key * option <= limit);
let opt = Array(keys.length).fill(option);
options = [...options
, opt.length < options[0].length
? [...opt, ...Array(options[0].length - opt.length).fill(0)]
: opt
];
}
rounds[limit] = [...options];
}
}
}
}
return rounds
}
let opts = counteropts(prices, possibleWins);
console.log(opts);