Search code examples
iosswiftiphoneswift5

Swift ranking dictionary


I'm able to ranking a dictionary of string and int. But my solution look like no smart and "swifty"

The problem is when more teams have same points and same rank as "team 2" and "team 4"

ex dic input

var dict:[String:Int] = ["team1":79,"team2":5, "team3":18, "team4":5, "team5": 82, "team6":1]

output

[(team: "team5", rank: 1), (team: "team1", rank: 2), (team: "team3", rank: 3), (team: "team2", rank: 4), (team: "team4", rank: 4), (team: "team6", rank: 5)]

the code:

var ris = [(team:String,rank:Int)]()
var pos = 1

let sorted = dict.sorted(by:{$0.value > $1.value})
print(sorted)
for (i, element) in sorted.enumerated() {

    if i == 0 ||  element.value == sorted[i-1].value {

    }
    else {
        pos += 1
    }
    ris.append((team:element.key,rank:pos))
}
let ranking = ris.sorted(by:{$0.rank < $1.rank})
print(ranking)

prints:

[(team: "team5", rank: 1), (team: "team1", rank: 2), (team: "team3", rank: 3), (team: "team2", rank: 4), (team: "team4", rank: 4), (team: "team6", rank: 5)]

ok it's works, but I'm sure I'm missing something better using some closure of sorted, maps flapmaps etc

anyone?


Solution

  • You can simplify it a bit by grouping teams with identical scores in a dictionary first. Then sort the dictionary (according to decreasing score), enumerate it (to get the offsets) and build the ranking list:

    let dict:[String:Int] = ["team1":79, "team2":5, "team3":18, "team4":5, "team5": 82, "team6": 1]
    
    let ranking = Dictionary(grouping: dict, by: { $0.value })
        .sorted(by: { $0.key > $1.key })
        .enumerated()
        .flatMap { (offset, elem) in
            elem.value.map { (team: $0.key, rank: offset + 1 )}
        }
    
    print(ranking)
    // [(team: "team5", rank: 1), (team: "team1", rank: 2),
    //  (team: "team3", rank: 3), (team: "team2", rank: 4),
    //  (team: "team4", rank: 4), (team: "team6", rank: 5)]]
    

    A detailed explanation:

    Dictionary(grouping: dict, by: { $0.value })
    

    creates a dictionary whose keys are the team scores, and whose values are arrays of the teams with that score.

    .sorted(by: { $0.key > $1.key })
    

    sorts the dictionary by decreasing keys, the result is an array of tuples:

     [(key: 82, value: [(key: "team5", value: 82)]),
      (key: 79, value: [(key: "team1", value: 79)]),
      (key: 18, value: [(key: "team3", value: 18)]),
      (key: 5, value: [(key: "team2", value: 5), (key: "team4", value: 5)]),
      (key: 1, value: [(key: "team6", value: 1)])]
    

    Then

    .enumerated()
    

    creates a lazy sequence of (offset, element) pairs from this array:

      (offset: 0, element: (key: 82, value: [(key: "team5", value: 82)])),
      (offset: 1, element: (key: 79, value: [(key: "team1", value: 79)])),
      (offset: 2, element: (key: 18, value: [(key: "team3", value: 18)])),
      (offset: 3, element: (key: 5, value: [(key: "team2", value: 5), (key: "team4", value: 5)])),
      (offset: 4, element: (key: 1, value: [(key: "team6", value: 1)]))
    

    Finally flatMap calls the closure on each (offset, element) pair and concatenates the result. Inside the closure,

     elem.value.map { (team: $0.key, rank: offset + 1 )}
    

    maps an (offset, element) pair and to an array of (team, rank) tuples. For example,

      (offset: 3, element: (key: 5, value: [(key: "team2", value: 5), (key: "team4", value: 5)]))
    

    is mapped to

     [(team: "team2", rank: 4), (team: "team4", rank: 4)]
    

    flatMap() concatenates these arrays, giving the final ranking array.


    This is the originally posted solution, which produces the ranks 1, 2, 3, 4, 4, 6 for the sample data (instead of 1, 2, 3, 4, 4, 5):

    let dict:[String:Int] = ["team1":79, "team2":5, "team3":18, "team4":5, "team5": 82, "team6": 1]
    
    var ranking = [(team:String,rank:Int)]()
    for (_, list) in Dictionary(grouping: dict, by: { $0.value })
        .sorted(by: { $0.key > $1.key }) {
            let pos = ranking.count + 1
            ranking.append(contentsOf: list.map { ($0.key, pos )})
    }
    
    print(ranking)
    // [(team: "team5", rank: 1), (team: "team1", rank: 2),
    //  (team: "team3", rank: 3), (team: "team4", rank: 4),
    //  (team: "team2", rank: 4), (team: "team6", rank: 6)]