如何按属性对对象进行排名?

时间:2016-03-02 02:23:25

标签: javascript

我正在尝试按属性值对对象集合进行排名。基本上每个玩家对象都有一个得分属性,应该从最高到最低排名。例如:

{
    player1: {
        nickname: "Bob",
        score: 100,
        rank: 4
    },
    player2: {
        nickname: "Amy",
        score: 200,
        rank: 3
    },
    player3: {
        nickname: "Grant",
        score: 300,
        rank: 2
    },
    player4: {
        nickname: "Steve",
        score: 200,
        rank: 3
    },
    player5: {
        nickname: "Joe",
        score: 500,
        rank: 1
    }
}

按分数对这些对象进行排名应该会产生以下结果:

var array = [];

for (var key in players) {
    array.push(players[key]);

}

array.sort(function(a, b){
    return a.score - b.score;
});

for (var i = 0; i < array.length; i++) {
    array[i].rank = i + 1;
}

重要的是,具有相同分数的对象应具有相同的等级。

据我所知:

Option Explicit
Public StopLoop As Boolean
Sub GetHtmlTable()
StopLoop = False
Do Until StopLoop = True
DoEvents
    Dim objWeb As QueryTable

    Sheets(1).Columns(1).ClearContents

    With Sheets("Sheet1")
        Set objWeb = .QueryTables.Add( _
        Connection:="URL;http://www.spotternetwork.org/feeds/gr.txt", _
        Destination:=.Range("A1"))
        With objWeb
            .WebSelectionType = xlSpecifiedTables
            .WebTables = "1" ' Identify your HTML Table here
            .Refresh BackgroundQuery:=False
            .SaveData = True
        End With
    End With
    Set objWeb = Nothing


'End Import of Text From http://www.spotternetwork.org/feeds/gr.txt==================
'Start Filter Out Unused Data========================================================
Dim i As Long
Dim j As Long
Dim LRow As Long
Dim LListRow As Long
Dim BMatch As Boolean

'Find last instance of "End:" in
LRow = Sheets(1).Range("A:A").Find(what:="End*", searchdirection:=xlPrevious).Row
'Find last non-blank row in column A of second sheet
LListRow = Sheets(2).Range("A:A").Find(what:="*", searchdirection:=xlPrevious).Row

Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.DisplayAlerts = False
Application.EnableEvents = False

If LRow >= 11 Then
    'Make sure there are at least 11 rows of data
    i = LRow
    'MsgBox "First checkpoint: Last row of data is " & LRow 'Comment out this line
    Do
        BMatch = False
        For j = 1 To LListRow
            'Test this block to see if the value from j appears in the second row of data
            If InStr(1, Sheets(1).Range("A" & i - 2).Value2, Sheets(2).Range("A" & j).Value2) > 0 Then
                BMatch = True
                Exit For
            End If
        Next j
        'Application.StatusBar = "Match status for row " & i & ": " & BMatch
        If Not BMatch Then
            'Loop backwards to find the starting row (no lower than 11)
            For j = i To 11 Step -1
                If Sheets(1).Range("A" & j).Value2 Like "Object:*" Then Exit For
            Next j
            Sheets(1).Rows(j & ":" & i).Delete
            i = j - 1
        Else
            'Find next block
            If i > 11 Then
                For j = i - 1 To 11 Step -1
                    If Sheets(1).Range("A" & j).Value2 Like "End:*" Then Exit For
                Next j
                i = j
            Else
                i = 10 'Force the loop to exit
            End If
        End If
        'Application.StatusBar = "Moving to row " & i
    Loop Until i < 11

    'Loop back through and delete any blank rows
    LRow = Sheets(1).Range("A:A").Find(what:="*", searchdirection:=xlPrevious).Row
    'MsgBox "Second checkpoint: new last row of data is " & LRow
    For i = LRow To 11 Step -1
        If Sheets(1).Range("A" & i).Value2 = vbNullString Then Sheets(1).Rows(i).Delete
    Next i
End If

'Application.StatusBar = False
Application.EnableEvents = True
Application.DisplayAlerts = True
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True

'End Filter Out Unused Data========================================================
'Start Write To Local Txt File=====================================================
Dim sSaveAsFilePath As String
Application.DisplayAlerts = False


    sSaveAsFilePath = "C:\Users\Speedy\Desktop\Test\test.txt"

    Sheets(1).Copy '//Copy sheet 1 to new workbook
    ActiveWorkbook.SaveAs sSaveAsFilePath, xlTextWindows '//Save as text (tab delimited) file
    If ActiveWorkbook.Name <> ThisWorkbook.Name Then '//Double sure we don't close this workbook
        ActiveWorkbook.Close False
    End If
Application.DisplayAlerts = True
Application.Wait (Now + TimeValue("0:00:05"))
Loop
End Sub

Sub KillMacro()
  StopLoop = True ' stop that perpetual loop in Workbook_Open()
  MsgBox "Program Stopped"
End Sub

这会按照得分的顺序为每个对象分配一个“rank”属性,但是你怎么能给绑定的对象提供相同的等级数?

JSfiddle

4 个答案:

答案 0 :(得分:3)

如果您有幸能够利用一些新的ES6方法,您可以创建一组分数:

var scores = new Set(Object.keys(players).map(function (key) {
  return players[key].score;
}));
// > Set {100, 200, 300, 500}

然后将其转换为数组并对其进行排序:

var ordered_scores = Array.from(scores).sort(function(a, b) {
  return b - a;
});
// > [500, 300, 200, 100]

然后只是简单地更新你的球员:

Object.keys(players).forEach(function (key) {
  var player = players[key];
  player.rank = ordered_scores.indexOf(player.score) + 1;
});

&#13;
&#13;
var players = {
    player1: {
        nickname: "Bob",
        score: 100
    },
    player2: {
        nickname: "Amy",
        score: 200
    },
    player3: {
        nickname: "Grant",
        score: 300
    },
    player4: {
        nickname: "Steve",
        score: 200
    },
    player5: {
        nickname: "Joe",
        score: 500
    }
};

var scores = new Set(Object.keys(players).map(function (key) {
  return players[key].score;
}));

var ordered_scores = Array.from(scores).sort(function(a, b) {
  return b - a;
});

Object.keys(players).forEach(function (key) {
  var player = players[key];
  player.rank = ordered_scores.indexOf(player.score) + 1;
});

document.write('<pre>' + JSON.stringify(players, null, 2) + '</pre>');
&#13;
&#13;
&#13;

答案 1 :(得分:2)

jsfiddle:https://jsfiddle.net/2khtnjxw/1/

// change sort function
array.sort(function(a, b){
    return b.score - a.score;
});

var rank = 1;
for (var i = 0; i < array.length; i++) {
  // increase rank only if current score less than previous
  if (i > 0 && array[i].score < array[i - 1].score) {
    rank++;
  }
    array[i].rank = rank;
}

// result: 
[  
   {  
      "nickname":"Joe",
      "score":500,
      "rank":1
   },
   {  
      "nickname":"Grant",
      "score":300,
      "rank":2
   },
   {  
      "nickname":"Amy",
      "score":200,
      "rank":3
   },
   {  
      "nickname":"Steve",
      "score":200,
      "rank":3
   },
   {  
      "nickname":"Bob",
      "score":100,
      "rank":4
   }
]

答案 2 :(得分:0)

您正在为键数组指定rank属性,而您需要更新players对象,并检查相邻的值是否相同以获得相同的排名。你也在排序函数应该改变,以便按降序排序,根据你的代码,它按升序排序。

&#13;
&#13;
var players = {
  player1: {
    nickname: "Bob",
    score: 100
  },
  player2: {
    nickname: "Amy",
    score: 200
  },
  player3: {
    nickname: "Grant",
    score: 300
  },
  player4: {
    nickname: "Steve",
    score: 200
  },
  player5: {
    nickname: "Joe",
    score: 500
  }
};

// get keys from object
var arr = Object.keys(players);
// sort keys array based on score value
arr.sort(function(a, b) {
  return players[b].score - players[a].score;
});
// iterate key array and assign rank value to object
for (var i = 0, rank = 1; i < arr.length; i++) {
  // assign rank value
  players[arr[i]].rank = rank;
  // increment rank only if score value is changed
  if (players[arr[i + 1]] && players[arr[i]].score != players[arr[i + 1]].score)
    rank++;
}

document.write('<pre>' + JSON.stringify(players, null, 3) + '</pre>');
&#13;
&#13;
&#13;

答案 3 :(得分:0)

感谢@hamms。

我完成了打字稿(通用)的实现:

export function getRanks<T>(
  items: T[],
  getId: (item: T) => string,
  getScore: (item: T) => number,
): Map<string, number> {
  const itemScoresMap = items.reduce(
    (r, x) => r.set(getId(x), getScore(x)),
    new Map(),
  )

  const uniqueScores = new Set(itemScoresMap.values())
  const orderedScores = Array.from(uniqueScores).sort((a, b) => b - a)

  return items
    .map(x => {
      const id = getId(x)
      const score = itemScoresMap.get(id)

      return {
        id,
        rank: orderedScores.indexOf(score) + 1,
      }
    })
    .reduce((r, x) => r.set(x.id, x.rank), new Map())
}