Ранжирование элементов массива

мне нужен алгоритм для ранжирования элементов массива в JavaScript.

пример : у меня есть массив следующим образом:

[79, 5, 18, 5, 32, 1, 16, 1, 82, 13]

мне нужно ранжировать записи по значению. Таким образом, 82 должен получить ранг 1, 79 ранг 2 и т. д. Если две записи имеют одинаковое значение, они получают одинаковый ранг, а ранг для более низкого значения повышается.

таким образом, для этого массива новый массив ранжирования будет:

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

Как я могу это сделать ?

5 ответов


var arr = [79, 5, 18, 5, 32, 1, 16, 1, 82, 13];
var sorted = arr.slice().sort(function(a,b){return b-a})
var ranks = arr.slice().map(function(v){ return sorted.indexOf(v)+1 });

результат :

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

если вы хотите быть совместим со старыми браузерами, вам придется определите прокладку для indexOf и карта (обратите внимание, что если вы хотите сделать это очень быстро для очень больших массивов, вам лучше использовать for петли и использовать объект в качестве карты вместо indexOf).


Это не будет работать со старыми браузерами, потому что он использует особенности ECMAScript 5, но это позволяет быстро и лаконично создавать массив рейтингов даже для очень больших массивов. (Он не использует indexOf что делает линейный поиск и, следовательно, может быть медленным для больших массивов.)

function cmp_rnum(a,b) {
    // comparison function: reverse numeric order
    return b-a;
}
function index_map(acc, item, index) {
    // reduction function to produce a map of array items to their index
    acc[item] = index;
    return acc;
}
function ranks(v) {
    var rankindex = v.slice().sort(cmp_rnum).reduceLeft(index_map, Object.create(null));
    // reduceLeft() is used so the lowest rank wins if there are duplicates
    // use reduce() if you want the highest rank
    return v.map(function(item){ return rankindex[item]+1; });
}

пример:

> ranks([79, 5, 18, 5, 32, 1, 16, 1, 82, 13]);
  [2, 7, 4, 7, 3, 9, 5, 9, 1, 6]

function rank(arr, f) {
    return arr
    .map((x, i) => [x, i])
    .sort((a, b) => f(a[0], b[0]))
    .reduce((a, x, i, s) => (a[x[1]] =
        i > 0 && f(s[i - 1][0], x[0]) === 0 ? a[s[i - 1][1]] : i + 1, a), []);
}

использование:

rank([79, 5, 18, 5, 32, 1, 16, 1, 82, 13], (a, b) => b - a);
// [2, 7, 4, 7, 3, 9, 5, 9, 1, 6] 

выглядит немного уродливо, но он не использует indexOf() или объект / карта, поэтому он не только работает немного быстрее, но, что более важно, он уважает значение "той же ранжированности", как определено функция сравнения. Если использовать indexOf() или объект, "же rankedness" может означать только a === b или String(a) === String(b).

можно использовать findIndex():

function rank(arr, f) {
    const sorted = arr.slice().sort(f)
    return arr.map(x => sorted.findIndex(s => f(x, s) === 0) + 1)
}

мне нужен был тот же фрагмент кода для сценария планирования операций, который я писал. Я использовал объекты и их свойства / ключи, которые могут иметь любое значение и могут быть доступны при необходимости. Кроме того, насколько я читал в некоторых статьях, поиск свойств в объектах может быть быстрее, чем поиск в массивах.

скрипт ниже имеет три простых шага:

  1. сортировка значений (по возрастанию или по убыванию, не важно для остальных скрипт)

  2. найти ранги и количество вхождений для каждого значения

  3. замените заданные значения рангами, используя данные из шага 2

Внимание! Приведенный ниже скрипт не выводит повторяющиеся ранги, а вместо этого увеличивает ранги для повторяющихся значений / элементов.

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

мне также нужно было сделать что-то еще для моего сценария.

вышеуказанный выход имеет следующее смысл:

  • index-идентификатор элемента во входном массиве

  • value-ранг элемента из входного массива

и мне нужно было в основном "поменять индекс на значение", чтобы у меня был список идентификаторов элементов, расположенных в порядке их рангов:

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

и некоторые примеры:

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]


Я не очень хорош в Javascript, но в PHP это можно сделать довольно легко следующим образом. Кто-то хороший в JavaScript может придумать соответствующий код.

$marks = [79, 5, 18, 5, 32, 1, 16, 1, 82, 13];

public function getRank($marks) {
    $rank = 1; $count = 0; $ranks = [];
    //sort the marks in the descending order
    arsort($marks,1);
    foreach($marks as $mark) {
      //check if this mark is already ranked
      if(array_key_exists($mark, $ranks)) {
       //increase the count to keep how many times each value is repeated
       $count++;
       //no need to give rank - as it is already given
      } else {
        $ranks[$mark] = $i+$j;
        $i++;
      }
    return $ranks;
}