Home > Software engineering >  Rank an array of objects in Swift efficiently
Rank an array of objects in Swift efficiently

Time:02-02

I'm trying to rank an array of objects for a leaderboard.

I have something that is working, but I'm wondering if it's possible to code something cleaner and more efficient. Maybe with map() ?

This is what I did:

I have a Score object:

class Score {
  value: Double
  username: String
  date: Date,
  position: Int?
}

And this is my code (it's working but maybe without efficiency with a lot of data):

let scores: [Score] = [*allMyScoreObjects*]()

var i = 0
for score in scores {
     if i > 0 {
       let previousScore = scores[i - 1]
       if previousScore.value == score.value {
           score.position = previousScore.position
       } else {
           score.position = i   1
       }        
     } else {
         score.position = 1
     }
     i  = 1
}

Would you have any advice?

CodePudding user response:

If just sorting values:

scores.sort {$0.value > $1.value}

If sorting multiple criteria:

scores.sort {($0.value, $0.date) > ($1.value, $1.date)}

CodePudding user response:

To clarify you issues, let's illustrate it at the same time:
scores is a array of scores sorted by value.
You want to know their position, who's first, who's second, etc, getting also the ex-aequo, keeping their rank.

Let's simplify your example:

class Score: CustomStringConvertible {
    var value: Double
    var position: Int?

    init(value: Double, position: Int? = nil) {
        self.value = value
        self.position = position
    }

    var description: String { return "Score(value:\(value), position: \(position))"}
}

I added CustomStringConvertible to have a "beautiful print".

And with initial input:

let scores = [Score(value: 0.1), //#1
              Score(value: 0.8), //#2
              Score(value: 0.8), //#2
              Score(value: 1.2), //#3
              Score(value: 6.7)] //#4

So target val

A possible way to do that is to use reduce(into:_). I prefer it in this case, because it's easy to get "last" item.

let reduced1 = scores.reduce(into: [Score]()) { partialResult, aScore in
    defer { partialResult.append(aScore) }
    guard let last = partialResult.last else { aScore.position = 1; return }
    if last.value == aScore.value {
        aScore.position = last.position
    }
    else {
        aScore.position = (last.position ?? 0)   1
    }
}

print(reduced1)

partialResult will grow at each iteration, and will be in the end reduced1. We check the last saved item in partialResult, and if there isn't, we set the default position to 1, and if there is one, we compare the value between the current one (aScore) and last to know which position should aScore` have.

Output:

$>[Score(value:0.1, position: Optional(1)), Score(value:0.8, position: Optional(2)), Score(value:0.8, position: Optional(2)), Score(value:1.2, position: Optional(3)), Score(value:6.7, position: Optional(4))]

Which could be simplified with:

let reduced2 = scores.reduce(into: [Score]()) { partialResult, aScore in
    if partialResult.last?.value == aScore.value {
        aScore.position = (partialResult.last?.position ?? 0)   1
    }
    partialResult.append(aScore)
}

print(reduced2)

Which could be shorten with a ternary if:

let reduced3 = scores.reduce(into: [Score]()) { partialResult, aScore in
    aScore.position = partialResult.last?.value == aScore.value ? partialResult.last?.position : (partialResult.last?.position ?? 0)   1
    partialResult.append(aScore)
}

print(reduced3)

And could also be shorter with the Shorthand Argument Names (using $0 and $1 in our case):

let reduced4 = scores.reduce(into: [Score]()) {
    $1.position = $0.last?.value == $1.value ? $0.last?.position : ($0.last?.position ?? 0)   1
    $0.append($1)
}

print(reduced4)

I don't recommend the construction of reduced3 & reduced4 since it's hard to understand what's going on, and to debug, it's a nightmare. I might even keep reduced1 or reduced2 in my own code. I think that reduced1 is clearer for anyone looking at the code. Also, compiling will make optimisation, so don't worry too much about it. I'd prioritize clarity.

  •  Tags:  
  • Related