0

I have an array of LeagueTeam I need to sort. A League team has the usual stuff, points, shotsFor, shotsAgainst, totalShots etc. And the League will have an array of LeagueSortOptions such as [.points, .shots, .shotsFor]. This would sort by points then total shots then by shots for.

The leagueSortOptions are created by the user when creating the league so could be in any order depending on the user's needs eg.

[.points, .shots, .shotsFor]
[.shots, .shotsFor]
[.shotsFor, .points]

I will probably need to add more sort options down the line so I feel some sort of recursion is needed rather than creating a function for every single permutation possible. But in just can't wrap my head around it, been going round in circles for hours now. Any help or a pointer in the right direction would be greatly appreciated, thanks :)

enum LeagueSortOptions: String, Codable {
      case points = "Points", shots = "Shots", shotsFor = "Shots For"
}


struct LeagueTeam : Codable {
    var name: String
    var gamesPlayed: Int
    var gamesWon: Int
    var gamesLost: Int
    var gamesDrawn: Int
    var shots: Int
    var points: Int

    var shotsFor: Int
    var shotsAgainst: Int
}

I currently use this code to sort by points and then by shotsFor, but this only accounts for one permutation, which will probably be the most common:

    teams.sort { (team1, team2) -> Bool in
        if team1.points == team2.points {
            if team1.shots > team2.shots { return true } else { return false }
        }
        if team1.points > team2.points { return true } else { return false }
    }
James
  • 13
  • 2
  • Look into [sort(by:)](https://developer.apple.com/documentation/swift/array/1688499-sort) – Razi Tiwana Apr 15 '19 at 09:17
  • You could use KeyPath, but you need a mapping from the enum to the KeyPath of the property you want to sort by. – keji Apr 15 '19 at 09:22
  • Have a look at the answers here. I think it can help you https://stackoverflow.com/questions/43056807/sorting-a-swift-array-by-ordering-from-another-array – axel Apr 15 '19 at 09:23
  • teams.sort { (team1, team2) -> Bool in if team1.points == team2.points { if team1.shots > team2.shots { return true } else { return false } } if team1.points > team2.points { return true } else { return false } } – James Apr 15 '19 at 09:27
  • I use this just now to sort by points then shots for which will probably be the most common, it works fine but is only one permutation. Thank you for the replies i will look in these now :) – James Apr 15 '19 at 09:28
  • @James what you have added here can be achieved in a more compact way `teams.sort(by: {($0.points == $1.points) ? $0.points > $1.points : $0.shots > $1.shots }) ` – Razi Tiwana Apr 15 '19 at 09:41
  • yes that is much more elegant, thank you :) – James Apr 15 '19 at 10:36

2 Answers2

0

I think stable sorting would be the solution. Here is a comment from @Esqarrouth how to implement it: How to stable sort an array in Swift.

Once you write sorting algorithm for every field (separately!) you can combine them:

extension LeagueSortOptions {
    func compare(lhs: LeagueTeam, rhs: LeagueTeam) -> ComparisonResult {
        switch self {
            // return comparison result for all LeagueSortOptions
        }
    }
}

// ...
func sort(initialItems: [LeagueTeam], by sortOptions: [LeagueSortOptions]) -> [LeagueTeam]
    var sortedItems = initialItems

    // depending on needs you may want to reverse sortOptions
    for sortOption in sortOptions { 

        // stableSort would call "compare" internally on sortOption
        sortedItems = stableSort(sortOption)
    }
    return sortedItems
}
anakkin
  • 731
  • 1
  • 6
  • 21
0

You may need to prepare something like this:

extension LeagueTeam {
    func compare(to other: LeagueTeam, by sortOptions: [LeagueSortOptions]) -> ComparisonResult {
        for option in sortOptions {
            let num1 = self.intProperty(for: option)
            let num2 = other.intProperty(for: option)
            if num1 < num2 {
                return .orderedAscending
            } else if num1 > num2 {
                return .orderedDescending
            }
        }
        return .orderedSame
    }

    func intProperty(for sortOption: LeagueSortOptions) -> Int {
        switch sortOption {
        case .points:
            return self.points
        case .shots:
            return self.shots
        case .shotsFor:
            return self.shotsFor
        //`LeagueSortOptions` would have more cases?
        //...
        }
    }
}

(You may be able to write intProperty(for:) in a more Swifty manner using KeyPath, but that's another issue.)

And you can sort your array as:

var teams: [LeagueTeam] = [
    LeagueTeam(name: "a", gamesPlayed: 0, gamesWon: 0, gamesLost: 0, gamesDrawn: 0, shots: 0, points: 100, shotsFor: 10, shotsAgainst: 20),
    LeagueTeam(name: "b", gamesPlayed: 0, gamesWon: 0, gamesLost: 0, gamesDrawn: 0, shots: 0, points: 100, shotsFor: 20, shotsAgainst: 10),
    LeagueTeam(name: "c", gamesPlayed: 0, gamesWon: 0, gamesLost: 0, gamesDrawn: 0, shots: 0, points: 110, shotsFor: 120, shotsAgainst: 100),
]
var sortOptions: [LeagueSortOptions] = [.points, .shots, .shotsFor]

teams.sort {(team1, team2) -> Bool in
    return team1.compare(to: team2, by: sortOptions) == .orderedDescending
}
print(teams.map{$0.name}) //->["c", "b", "a"]
OOPer
  • 47,149
  • 6
  • 107
  • 142
  • thank you so much for your reply, this looks like what i have been trying but have been unable to do. I will be trying this out today :) – James Apr 15 '19 at 10:09
  • This seems to be working perfectly, thank you so much for taking time to reply. I couldn't seem to grasp the link between enum and property that the intProperty function provides. I started down the wrong path about 2 days ago and couldn't recover lol. I have been learning swift and iOS development in my spare time for a couple of years and this has reinforced the fact that I need to go back and learn some more of the basics :) – James Apr 15 '19 at 10:34