0

I'm creating a leaderboard of top 5 scores that contains the following elements:

Player Name

Completion Time (how long it took to complete the game)

Total Moves

Date Stamp

To do this, I have created an array of tuples with a nested tuple to track minutes, seconds, and milliseconds as seen below:

var leaderBoard: [(playerName: String, completionTime: (minutes: Int, seconds: Int, miliseconds: Int), totalMoves: Int, dateStamp: Date)] = []

When a game is completed, these values are appended to the array until it contains a total of 5 elements.

My trouble is that I need to sort this array by completion time in ascending order. Due to the complicated, nested nature of the tuples in this array, I can't seem to find a valid way of accomplishing this. Any assistance would be greatly appreciated.

1
  • 3
    This is a poor data structure, you'd be better with using structs/classes. Tuples are not intended to be models... Commented Sep 21, 2018 at 5:03

3 Answers 3

2

If your completionTime is well formed (seconds is in 0...59 and milliseconds is in 0...999), then you can sort your leaderboard with:

leaderBoard.sort { $0.completionTime < $1.completionTime }

This works because Swift can compare 2 tuples (1, 2, 3) and (1, 2, 4) with < and it will compare the first items, and if they're equal, it will compare the second items, and if they're equal it will compare the third. So you can order them with a simple < comparison. This works even if the items are labelled as long as the two tuples have the same number of elements, the number of elements are 6 or fewer, and the types of the corresponding elements match.


Example:

var leaderBoard: [(playerName: String, completionTime: (minutes: Int, seconds: Int, milliseconds: Int), totalMoves: Int, dateStamp: Date)] = [
    (playerName: "Fred", completionTime: (minutes: 4, seconds: 10, milliseconds: 800), totalMoves: 3, dateStamp: Date()),
    (playerName: "Barney", completionTime: (minutes: 5, seconds: 10, milliseconds: 800), totalMoves: 3, dateStamp: Date()),
    (playerName: "Wilma", completionTime: (minutes: 4, seconds: 10, milliseconds: 801), totalMoves: 3, dateStamp: Date()),
    (playerName: "Bam Bam", completionTime: (minutes: 1, seconds: 10, milliseconds: 0), totalMoves: 3, dateStamp: Date()),
    (playerName: "Pebbles", completionTime: (minutes: 4, seconds: 10, milliseconds: 799), totalMoves: 3, dateStamp: Date())
]

leaderBoard.sort { $0.completionTime < $1.completionTime }
leaderBoard.forEach { print($0) }

Output:

(playerName: "Bam Bam", completionTime: (minutes: 1, seconds: 10, milliseconds: 0), totalMoves: 3, dateStamp: 2018-09-21 11:17:36 +0000)
(playerName: "Pebbles", completionTime: (minutes: 4, seconds: 10, milliseconds: 799), totalMoves: 3, dateStamp: 2018-09-21 11:17:36 +0000)
(playerName: "Fred", completionTime: (minutes: 4, seconds: 10, milliseconds: 800), totalMoves: 3, dateStamp: 2018-09-21 11:17:36 +0000)
(playerName: "Wilma", completionTime: (minutes: 4, seconds: 10, milliseconds: 801), totalMoves: 3, dateStamp: 2018-09-21 11:17:36 +0000)
(playerName: "Barney", completionTime: (minutes: 5, seconds: 10, milliseconds: 800), totalMoves: 3, dateStamp: 2018-09-21 11:17:36 +0000)
Sign up to request clarification or add additional context in comments.

3 Comments

Cool. I didn't know that tuples supported comparison like this. (Voted)
BTW, the output from leaderBoard.forEach {print($0)} puts each entry on a new line, so it's much more readable.
Thanks for the suggestion, @DuncanC. That is much more readable.
0

The sorted function does this job nicely. It takes a closure that compares 2 items from the array and returns true if the first is less than the second.

You just need to write a closure that calculates a value for each completionTime and compares them. It's faster to do integer math, so the following converts each completionTime to integer milliseconds and compares those values:

let sortedPlayers = leaderBoard.sorted { lhs, rhs in
    let lhTime =  lhs.completionTime.minutes * 60_000 + lhs.completionTime.seconds * 1000 + 
      lhs.completionTime.miliseconds
    let rhTime =  rhs.completionTime.minutes * 60_000 + rhs.completionTime.seconds * 1000 + 
      rhs.completionTime.miliseconds
    return lhTime < rhTime
}

2 Comments

Even easier: just directly compare lhs.completionTime < rhs.completionTime. See my answer.
Thanks for accepting my answer, but I gotta say @vacawama 's answer is better. It's simpler and cleaner.
0
func recordWin() {
    let newEntry: (playerName: String, completionTime: (minutes: Int, seconds: Int, miliseconds: Int), totalMoves: Int, dateStamp: Date) = (playerName, completionTime, totalMoves, dateStamp)

    if leaderBoard.count < 5 {
        leaderBoard.append(newEntry)
    }

    else {

    }

    if leaderBoard.count > 1 {
        for _ in 0...4 {
            var currentIndex = 0

            for score in leaderBoard {
                if currentIndex > 0 {
                    if score.completionTime.minutes < leaderBoard[currentIndex - 1].completionTime.minutes {
                        leaderBoard.remove(at: currentIndex)
                        leaderBoard.insert(score, at: currentIndex - 1)
                    }

                    else if score.completionTime.minutes == leaderBoard[currentIndex - 1].completionTime.minutes {
                        if score.completionTime.seconds < leaderBoard[currentIndex - 1].completionTime.seconds {
                            leaderBoard.remove(at: currentIndex)
                            leaderBoard.insert(score, at: currentIndex - 1)
                        }

                        else if score.completionTime.seconds == leaderBoard[currentIndex - 1].completionTime.seconds {
                            if score.completionTime.miliseconds < leaderBoard[currentIndex - 1].completionTime.seconds {
                                leaderBoard.remove(at: currentIndex)
                                leaderBoard.insert(score, at: currentIndex - 1)
                            }
                        }
                    }
                }

                currentIndex += 1
            }
        }
    }

    if leaderBoard.count > 5 {
        leaderBoard.removeLast()
    }

    for score in leaderBoard {
        print(score.playerName + ": " + "Completion Time: " + "Total Moves: " + score.totalMoves.description + "Completion Time: " + score.completionTime.minutes.description + ":" + score.completionTime.seconds.description + ":" + score.completionTime.miliseconds.description + " - " + score.dateStamp.description)
    }
}

1 Comment

Gack. That's really complicated, and I'm not clear on if/how it works. See my answer. MUCH simpler.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.