【问题标题】:Ranking array elements排列数组元素
【发布时间】:2013-01-27 21:05:20
【问题描述】:

我需要一种算法来对 Javascript 中的数组元素进行排名。

示例:我有一个数组如下:

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

我需要按值对条目进行排名。所以82 应该获得排名179 排名2 等。 如果两个条目具有相同的值,它们将获得相同的排名,并且较低值的排名会提高。

所以对于这个数组,新的排名数组是:

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

我该怎么做?

【问题讨论】:

  • 数组中的数字总是唯一的吗?编辑:根据示例,显然不是。
  • 这种模式没有意义。你能更好地描述问题吗?
  • 他们显然不是唯一的问题,2 5s 和 2 1s
  • 您在发布问题之前自己尝试过吗?
  • 对我来说好像是作业......

标签: javascript arrays algorithm sorting ranking


【解决方案1】:

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.map(function(v){ return sorted.indexOf(v)+1 });
console.log(ranks);

结果:

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

如果你想兼容旧的浏览器,你可能需要define a shim for indexOfmap(注意,如果你想对非常大的数组快速做到这一点,你最好使用for 循环并使用对象作为地图而不是indexOf)。

【讨论】:

  • 看来你们没有读懂这个问题。看来 OP 需要得到一个排名数组。
  • 排序错误的老问题^^ .sort(function(a,b){return a-b})
  • @Christoph 哦……你在我之前就看到了。我花了几分钟试图检查什么是真正的 OP 问题......
  • 地图可能也需要填充 (IE-9)。但非常好的解决方案!
  • 这对多个重复条目真的有效吗?
【解决方案2】:

这不适用于旧浏览器,因为它使用ECMAScript 5 features,但它允许您快速简洁地生成一个排名数组,即使对于非常大的数组也是如此。 (它不使用 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]

【讨论】:

  • 我不得不使用reduceRight
  • 这真的是正确的结果吗?有 8 个唯一值。怎么可能排在第9位?没有任何东西排在第 8 位。我认为这个解决方案无效,但我没有代表投票否决。
  • @ChrisNadovich 结果与 OP 的期望结果和接受答案的结果相同。 OP 说:“如果两个条目具有相同的值,它们将获得相同的排名,并且较低值的排名会提高。” 8 升到 9。
  • 好的,@FrancisAvila,我的立场是正确的。我想我被自己的要求蒙蔽了双眼,这是为了在有关系时仅对唯一值进行排名。
  • uncaught TypeError: reduceLeft is not a function。看起来应该是reduce
【解决方案3】:
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() 或对象/地图,所以它不仅运行速度快一点,而且更重要的是,它尊重了由comparison function。如果使用indexOf() 或对象,“相同排名”只能表示a === bString(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)
}

【讨论】:

  • rankNumbers([2, 5, 5, 10, 8], (a, b) => a - b) 返回[ 1, 2, 2, 5, 4 ],即跳过排名3。我知道重复的数字被赋予相同的排名,但是跳过排名 3 是否有意义?也许确实如此;好奇你的判断
  • 您不认为两者都应该具有排名(2 + 3) / 2 = 2.5,因为它们共享排名 2 和排名 3?
  • 我同意你的看法@Tom。上述算法似乎不正确。他们跳过排名值。
【解决方案4】:

JavaScript ES6 简单的两行解决方案。

var arrayRankTransform = arr => {
  const sorted = [...arr].sort((a, b) => b - a);
  return arr.map((x) => sorted.indexOf(x) + 1);
};

console.log(arrayRankTransform([79, 5, 18, 5, 32, 1, 16, 1, 82, 13]));

【讨论】:

    【解决方案5】:

    我不擅长 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;
    }
    

    【讨论】:

    • 我不知道为什么这个被否决了。它有一些问题,但似乎是唯一适合成为正确解决方案的问题。只需去掉 +$j 和 $count。 $i 仅对唯一值递增。
    【解决方案6】:

    我需要相同的代码来编写我正在编写的操作调度脚本。我使用了对象及其属性/键,它们可以具有任何值并且可以在需要时访问。另外,据我在一些文章中读到,在对象中搜索属性可能比在数组中搜索更快。

    下面的脚本有三个简单的步骤:

    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 - 输入数组中元素的ID

    • value - 输入数组中元素的排名

    而且我基本上需要“用值交换索引”,这样我就有了一个元素 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;}
    

    还有一些例子:

    待排名:

    [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]

    【讨论】:

      【解决方案7】:

      恕我直言,这里的几个解决方案是不正确的,因为它们不能正确处理重复值之后出现的值。这样的追随者应该获得下一个排名。最高排名应该等于数组中唯一值的数量。恕我直言,这个解决方案(在 PHP 中)是正确的。基本上@Suresh 的解决方案已删除了错误。

        function rank($marks){
          $rank = 1; $ranks = [];
          rsort($marks,SORT_NUMERIC);
          foreach($marks as $mark) {
            if(!isset($ranks[$mark])) {
              $ranks[$mark] = $rank++;
            }
          }
          return $ranks;
         }
      

      【讨论】:

        【解决方案8】:

        这应该适用于数组中的重复键

        function rank(arry) {
            let sorted = arry.slice().sort(function (a, b) {
                return b - a
            });
        
        
            let currentRank = sorted.length;
            let rankValue = null;
            let ranks = [];
        
            sorted.forEach(value => {
                if(value !== rankValue && rankValue !==null) {
                    currentRank--;
                }
        
                ranks.push({value,currentRank});
                rankValue = value;
            });
        
            let mapRanksToArrayValues = arry.map(function (x) {
                let _rank = null;
                ranks.forEach( rank => {
                    if(rank.value === x ) {
                        _rank =  rank.currentRank;
                        return;
                    }
                });
                return _rank;
            });
        
            return mapRanksToArrayValues;
        }
        

        【讨论】:

          【解决方案9】:

          我创建了 Rank_JS Pro。

          <script>https://cdn.statically.io/gl/maurygta2/mquery/master/Rank Tools/rank.js</script>
          

          基本方法:

          var a = {
            b: 2,
            c: 7
          }
          Rank_Tools.rank(a,(pos,name,value) => {
            return pos + ". "+name+" "+value;
          })
          // result
          // rank1 = 1. c 7
          // rank 2 = 2. b 2
          

          【讨论】:

            【解决方案10】:

            这种替代方式不需要对输入数组进行排序:

            // O(n^2)
            const rank = (arr) => {
              // Create a temporary array to keep metadata 
              // regarding each entry of the original array
              const tmpArr = arr.map(v => ({
                value: v,
                rank: 1,
              }));
            
              // Get rid of douplicate values
              const unique = new Set(arr);
            
              // Loops through the set
              for (let a of unique) {
                for (let b of tmpArr) {
                  // increment the order of an element if a larger element is pressent
                  if (b.value < a) {
                    b.rank += 1;
                  }
                }
              }
            
              // Strip out the unnecessary metadata 
              return tmpArr.map(v => v.rank);
            };
            
            console.log(rank([2600, 200, 36, 36, 400, 2, 0, 0]));
            // => [1, 3, 4, 4, 2, 5, 6, 6]
            
            console.log(rank([79, 5, 18, 5, 32, 1, 16, 1, 82, 13]));
            // => [2, 7, 4, 7, 3, 8, 5, 8, 1, 6]

            【讨论】:

              【解决方案11】:

              我有同样的作业,这个作业很好,如果你是新手,也更容易理解。

              function rankings(arr) {
                  let rankingsArr = [];
                  for (let i = 0; i < arr.length; i++) {
                      var rank = 1;
                      for (let j = 0; j < arr.length; j++) {
                          if (arr[j] > arr[i]) rank++;
                      }
                      rankingsArr.push(rank);
                  }
                  return rankingsArr;
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2014-07-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2021-04-22
                • 2012-07-24
                • 1970-01-01
                相关资源
                最近更新 更多