简体   繁体   English

JavaScript 数组排序/排名相同的排名

[英]JavaScript Array Sorting/Ranking with Equal Ranks

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);

Created a solution that worked, albeit ugly.创建了一个有效的解决方案,尽管很丑陋。 Thanks jamie for some framework used in this:感谢 jamie 在此使用的一些框架:

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);

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.第二次尝试:虽然不完全是 - 我认为将排名分离到不同的属性而不是依赖 indexOf 来查找排名是要走的路。 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 compare.
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.为了方便起见,我将使用 lodash,您应该明白了。

_.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.此时您失去了使用 indexOf 的能力,因此您需要编写自己的 getRank。

Again, with the help of lodash再次,在 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.net/4WJN3/1/完整的工作小提琴: http : //jsfiddle.net/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:下面的脚本包含三个简单的步骤:

  1. sort the values (ascending or descending doesn't matter for the rest of the script)对值进行排序(升序或降序对于脚本的其余部分无关紧要)

  2. find the ranks and number of occurrences for each value找到每个值的等级和出现次数

  3. replace the given values with ranks using the data from step 2使用步骤 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 become a key here and have a rank assigned
var ranksCount = {}; // each value from the input array will become 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 index - 输入数组中元素的 ID

  • value - the rank of the element from the input array value - 输入数组中元素的等级

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:我需要基本上“用值交换索引”,以便我有一个元素 ID 列表,按它们的等级顺序排列:

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: ToBeRanked:

[36, 33, 6, 26, 6, 9, 27, 26, 19, 9] [36, 33, 6, 26, 6, 9, 27, 26, 19, 9]

[12, 12, 19, 22, 13, 13, 7, 6, 13, 5] [12, 12, 19, 22, 13, 13, 7, 6, 13, 5]

[30, 23, 10, 26, 18, 17, 20, 23, 18, 10] [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, 7, 7, 7, 7, 7]

[7, 7, 7, 7, 7, 2, 2, 2, 2, 2] [7, 7, 7, 7, 7, 2, 2, 2, 2, 2]

[2, 2, 2, 2, 2, 7, 7, 7, 7, 7] [2, 2, 2, 2, 2, 7, 7, 7, 7, 7]

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

rankArrayElements( ToBeRanked ): rankArrayElements( ToBeRanked ):

[0, 1, 8, 3, 9, 6, 2, 4, 5, 7] [0, 1, 8, 3, 9, 6, 2, 4, 5, 7]

[5, 6, 1, 0, 2, 3, 7, 8, 4, 9] [5, 6, 1, 0, 2, 3, 7, 8, 4, 9]

[0, 2, 8, 1, 5, 7, 4, 3, 6, 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]

[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] [5, 6, 7, 8, 9, 0, 1, 2, 3, 4]

[9, 8, 7, 6, 5, 4, 3, 2, 1, 0] [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

convertRanksToListOfElementIDs( rankArrayElements( ToBeRanked ) ): convertRanksToListOfElementIDs(rankArrayElements(ToBeRanked)):

[0, 1, 6, 3, 7, 8, 5, 9, 2, 4] [0, 1, 6, 3, 7, 8, 5, 9, 2, 4]

[3, 2, 4, 5, 8, 0, 1, 6, 7, 9] [3, 2, 4, 5, 8, 0, 1, 6, 7, 9]

[0, 3, 1, 7, 6, 4, 8, 5, 2, 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]

[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] [5, 6, 7, 8, 9, 0, 1, 2, 3, 4]

[9, 8, 7, 6, 5, 4, 3, 2, 1, 0] [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);

Using ES6, here's how you can do it, adding a property rank to every client.使用 ES6,您可以这样做,为每个客户端添加属性rank 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'));

Concise, efficient, flexible.简洁、高效、灵活。
Items with same score have same ranks, yet the next different score get a rank shifted by n (based on index).具有相同分数的项目具有相同的等级,但下一个不同的分数的等级会移动n (基于索引)。 Input must be a array sorted by values of the sourceColumn .输入必须是sourceColumn的值排序数组 Two versions of the code, pick the one you like :两个版本的代码,选择你喜欢的一个:

  • for(){ } loop for(){ }循环
  • array.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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM