admin管理员组文章数量:1401281
After sorting an array of objects based on of their property values ("rating" in this case), how do you associate ranks for each object if there are ties between some of these values? Here's an example:
//Should be tied for 1st Rank
var obj1 = {
name: "Person1",
rating: 99
}
//Should be 3rd Rank
var obj2 = {
name: "Person2",
rating: 50
}
//Should be 2nd Rank
var obj3 = {
name: "Person3",
rating: 98
}
//Should be 4th Rank
var obj4 = {
name: "Person4",
rating: 0
}
//Should be tied for 1st Rank
var obj5 = {
name: "Person5",
rating: 99
}
Here's as far as I got:
var clients = [obj1, obj2, obj3, obj4, obj5];
var sorted = [];
for (var i = 0; i < clients.length; i++) {
sorted.push(clients[i]);
}
sorted.sort(function(a, b) {
return b.rating-a.rating;
});
Ultimately, I'd like to be able to get the rank using the object name, like this:
alert(sorted.indexOf(obj5) + 1);
After sorting an array of objects based on of their property values ("rating" in this case), how do you associate ranks for each object if there are ties between some of these values? Here's an example:
//Should be tied for 1st Rank
var obj1 = {
name: "Person1",
rating: 99
}
//Should be 3rd Rank
var obj2 = {
name: "Person2",
rating: 50
}
//Should be 2nd Rank
var obj3 = {
name: "Person3",
rating: 98
}
//Should be 4th Rank
var obj4 = {
name: "Person4",
rating: 0
}
//Should be tied for 1st Rank
var obj5 = {
name: "Person5",
rating: 99
}
Here's as far as I got:
var clients = [obj1, obj2, obj3, obj4, obj5];
var sorted = [];
for (var i = 0; i < clients.length; i++) {
sorted.push(clients[i]);
}
sorted.sort(function(a, b) {
return b.rating-a.rating;
});
Ultimately, I'd like to be able to get the rank using the object name, like this:
alert(sorted.indexOf(obj5) + 1);
Share
Improve this question
asked Jun 15, 2014 at 8:34
docta_faustusdocta_faustus
2,8395 gold badges32 silver badges51 bronze badges
7 Answers
Reset to default 2Created a solution that worked, albeit ugly. Thanks jamie for some framework used in this:
for (var i = 0; i < clients.length; i++) {
sorted.push(clients[i]);
}
sorted.sort(function(a, b) {
return b.rating-a.rating;
});
for(var i = 0; i < sorted.length; i++) {
// original ranking
sorted[i].rank = i + 1;
}
function sortRanking() {
for (var k = 0; k < sorted.length; k++) {
for (var h = 1; h < sorted.length + 1; h++) {
if (sorted[k+h] !== undefined) {
if (sorted[k+h].tie !== true) {
if (sorted[k].rating === sorted[h + k].rating) {
sorted[k].rank = k + 1;
sorted[h + k].rank = k + 1;
sorted[k].tie = true;
sorted[h + k].tie = true;
}
}
}
}
}
}
sortRanking();
alert("Rank: " + obj3.rank);
Using ES6, here's how you can do it, adding a property rank
to every client. Try the code snippet below.
function setRanks(clients) {
let currentCount = -1, currentRank = 0,
stack = 1; // consecutive clients with same rating
for (let i = 0; i < clients.length; i++) {
const result = clients[i];
if (currentCount !== result['rating']) {
currentRank += stack;
stack = 1;
} else {
stack++;
}
result['rank'] = currentRank;
currentCount = result['rating'];
}
}
// get the rank using the object name
function getRank(clientName) {
return clients.find(c => c.name === clientName)['rank'];
}
//Should be tied for 1st Rank
var obj1 = {
name: "Person1",
rating: 99
}
//Should be 3rd Rank
var obj2 = {
name: "Person2",
rating: 50
}
//Should be 2nd Rank
var obj3 = {
name: "Person3",
rating: 98
}
//Should be 4th Rank
var obj4 = {
name: "Person4",
rating: 0
}
//Should be tied for 1st Rank
var obj5 = {
name: "Person5",
rating: 99
}
var clients = [obj1, obj2, obj3, obj4, obj5];
clients.sort((c, other) => other.rating - c.rating);
setRanks(clients);
console.log(clients);
console.log(getRank('Person5'));
2nd attempt: although not quite there - i argue separating the ranking in to a different property rather than rely on the indexOf to find ranking is the way to go. You then have something clearer to manipulate when there is a tie. Still working it. Will be watching for best solution
for(var i = 0; i < sorted.length; i++) {
// original ranking
sorted[i].rank = i + 1;
}
function sortRanking() {
for(i=0; i< sorted.length; i++) {
var current = sorted[i];
var next = sorted[i + 1];
if(next === undefined || next.rating !== current.rating) {
console.log("we are done");
return "done";
}
if(next.rating === current.rating) {
for(var j = next + 1; j < sorted.length; j++) {
sorted[j].rank = sorted[j-1].rank;
}
next.rank = current.rank;
}
}
}
sortRanking();
console.log(sorted);
1st attempt - After playing around with for a bit. Here is a solution adding from your original logic:
var clients = [o1, o2, o3, o4];
var sorted = [];
for (var i = 0; i < clients.length; i++)
sorted.push(clients[i]);
sorted.sort(function (a, b) {
return clients.rating - clients.rating;
});
function checkForTieAndRating(x) {
// x parameter for object of interest
// need to get the one in front to determine if it is tied
// get index of obj of interest
var indexOfInterest = clients.indexOf(x);
var indexOfBefore = indexOfCurrent -1;
// if obj of interest is ranked #1 then return
if(indexOfBefore < 0) {
return indexOfInterest + 1;
} else {
// get the actual object before this one so you can check rating. put in variable so you can pare.
var objBefore = clients[indexOfBefore];
var ratingOfObjBefore = objBefore.rating;
if(ratingOfObjBefore === x.rating)
return "Tied for" + indexOfInterest;
}
}
// check ranking and if tie
checkForTieAndRating(obj2);
// other issue going this route - would be to then 1) alter the objects ranking following the objs that are tied - to
//Possible alternative solution: After working and about to submit it - I think it would be better to add a ranking property after the sort and manipulate the rankings from there if there are any tied.
If you want several records on the same place, you should probably use an additional immediate array, effectively grouping the elements.
I will use lodash for convinience, you should get the idea.
_.chain(clients).groupBy('rating').pairs().sortBy(0).reverse().pluck(1).value();
You loose your ability to use indexOf at this point, so you need to write your own getRank.
Again, with the help of lodash
// returns zero when no rank is found
var getRank = function(sortedArray, object) {
return 1 + _.findIndex(sortedArray, function(list) {
return _.contains(list, object);
});
};
Full working fiddle: http://jsfiddle/4WJN3/1/
I needed a similar piece of code for an operations scheduling script I was writing. I used objects and their properties/keys, which can have any value and can be accessed whenever needed. Also, as far as I read in some articles, the search of properties in objects can be faster than search in arrays.
The script below has three simple steps:
sort the values (ascending or descending doesn't matter for the rest of the script)
find the ranks and number of occurrences for each value
replace the given values with ranks using the data from step 2
Note! The below script will not output duplicate ranks, but instead increments ranks for duplicate values/elements.
function rankArrayElements( toBeRanked ) {
// STEP 1
var toBeRankedSorted = toBeRanked.slice().sort( function( a,b ) { return b-a; } ); // sort descending
//var toBeRankedSorted = toBeRanked.slice().sort( function( a,b ) { return a-b; } ); // sort ascending
var ranks = {}; // each value from the input array will bee a key here and have a rank assigned
var ranksCount = {}; // each value from the input array will bee a key here and will count number of same elements
// STEP 2
for (var i = 0; i < toBeRankedSorted.length; i++) { // here we populate ranks and ranksCount
var currentValue = toBeRankedSorted[ i ].toString();
if ( toBeRankedSorted[ i ] != toBeRankedSorted[ i-1 ] ) ranks[ currentValue ] = i; // if the current value is the same as the previous one, then do not overwrite the rank that was originally assigned (in this way each unique value will have the lowest rank)
if ( ranksCount[ currentValue ] == undefined ) ranksCount[ currentValue ] = 1; // if this is the first time we iterate this value, then set count to 1
else ranksCount[ currentValue ]++; // else increment by one
}
var ranked = [];
// STEP 3
for (var i = toBeRanked.length - 1; i >= 0; i--) { // we need to iterate backwards because ranksCount starts with maximum values and decreases
var currentValue = toBeRanked[i].toString();
ranksCount[ currentValue ]--;
if ( ranksCount[ currentValue ] < 0 ) { // a check just in case but in theory it should never fail
console.error( "Negative rank count has been found which means something went wrong :(" );
return false;
}
ranked[ i ] = ranks[ currentValue ]; // start with the lowest rank for that value...
ranked[ i ] += ranksCount[ currentValue ]; // ...and then add the remaining number of duplicate values
}
return ranked;}
I also needed to do something else for my script.
The above output has the following meaning:
index - the ID of the element in the input array
value - the rank of the element from the input array
And I needed to basically 'swap the index with the value', so that I have a list of element IDs, arranged in the order of their ranks:
function convertRanksToListOfElementIDs( ranked ) { // elements with lower ranks will be first in the list
var list = [];
for (var rank = 0; rank < ranked.length; rank++) { // for each rank...
var rankFound = false;
for (var elementID = 0; elementID < ranked.length; elementID++) { // ...iterate the array...
if ( ranked[ elementID ] == rank ) { // ...and find the rank
if ( rankFound ) console.error( "Duplicate ranks found, rank = " + rank + ", elementID = " + elementID );
list[ rank ] = elementID;
rankFound = true;
}
}
if ( !rankFound ) console.error( "No rank found in ranked, rank = " + rank );
}
return list;}
And some examples:
ToBeRanked:
[36, 33, 6, 26, 6, 9, 27, 26, 19, 9]
[12, 12, 19, 22, 13, 13, 7, 6, 13, 5]
[30, 23, 10, 26, 18, 17, 20, 23, 18, 10]
[7, 7, 7, 7, 7, 7, 7, 7, 7, 7]
[7, 7, 7, 7, 7, 2, 2, 2, 2, 2]
[2, 2, 2, 2, 2, 7, 7, 7, 7, 7]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
rankArrayElements( ToBeRanked ):
[0, 1, 8, 3, 9, 6, 2, 4, 5, 7]
[5, 6, 1, 0, 2, 3, 7, 8, 4, 9]
[0, 2, 8, 1, 5, 7, 4, 3, 6, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[5, 6, 7, 8, 9, 0, 1, 2, 3, 4]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
convertRanksToListOfElementIDs( rankArrayElements( ToBeRanked ) ):
[0, 1, 6, 3, 7, 8, 5, 9, 2, 4]
[3, 2, 4, 5, 8, 0, 1, 6, 7, 9]
[0, 3, 1, 7, 6, 4, 8, 5, 2, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[5, 6, 7, 8, 9, 0, 1, 2, 3, 4]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
function rank(arr) {
var ret = [];
var s = [];
var i = 0;
var _key_;
for (_key_ in arr) {
var v;
v = arr[_key_];
if (!s[v]) {
s[v] = ++i;
}
ret.push( {
'Mark': v,
'Rank': s[v]
});
}
return ret;
}
var marks = [
65,
41,
38,
38,
37,
37,
92,
84,
84,
84,
83
];
marks.sort(function(a, b) {
return b-a;
});
var rank = rank(marks);
console.log(rank);
Concise, efficient, flexible.
Items with same score have same ranks, yet the next different score get a rank shifted by n
(based on index). Input must be a array sorted by values of the sourceColumn
. Two versions of the code, pick the one you like :
for(){ }
looparray.map()
var studentsSortedByGrades = [
{ name: "A", grade: 5 },
{ name: "B", grade: 3 },
{ name: "C", grade: 3 },
{ name: "D", grade: 2 },
];
var addRankFORLOOP = function(sortedArr,sourceColumn,newColumn){
for(var i = 0; i<sortedArr.length; i++){ //
sortedArr[i][newColumn] =
i===0 || sortedArr[i][sourceColumn] !== sortedArr[i-1][sourceColumn] ? i+1 // anytime new grade appears, rank=i
: sortedArr[i-1][newColumn] // elseIf: equal grade, then equal rank
}
return sortedArr;
};
/*//OR
var addRankMAP = function(sortedArr,sourceColumn,newColumn){
return sortedArr.map((item,i) => {
item[newColumn] = i===0 || sortedArr[i][sourceColumn] !== sortedArr[i-1][sourceColumn] ? i+1 // anytime new grade appears, rank=i
: sortedArr[i-1][newColumn] // elseIf: equal grade, then equal rank
return item; })
}; /**/
var withRanks = addRankFORLOOP(studentsSortedByGrades,'grade','rank');
console.log(withRanks) // ranks: 1,2,2,4
本文标签: JavaScript Array SortingRanking with Equal RanksStack Overflow
版权声明:本文标题:JavaScript Array SortingRanking with Equal Ranks - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1744200990a2594959.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论