Reputation: 211
I have 2 arrays. One for players and one for scores. e.g.
var players = ["Bill", "Bob", "Sam", "Dave"]
var scores = [10,15,12,15]
I can find the index of the (first) max score (and the winner's name) by using:
let highScore = scores.max()
let winningPlayerIndex = scores.index(of: highScore!)
let winningPlayer = players[winningPlayerIndex!]
This works fine if there is only one player with the highest score but how would I return multiple indices (i.e. 1 and 3 in this example) for all values that are equal to the max value? I need the indices to then map back to the players array to pull out the names of all the players with the highest score. Or is there a better way to do all of this?
Upvotes: 16
Views: 17668
Reputation: 236360
You can zip the collection indices with its elements and get the minimum value using collection min method and pass a predicate to compare the elements. Get the result and extract the index of the tuple:
let numbers = [2, 4, 4, 2, 3, 1]
let minIndex = zip(numbers.indices, numbers).min(by: { $0.1 < $1.1 })?.0 // 5
let maxIndex = zip(numbers.indices, numbers).max(by: { $0.1 < $1.1 })?.0 // 1
As an extension where the elements are comparable:
extension Collection where Element: Comparable {
func firstIndexOfMaxElement() -> Index? {
zip(indices, self).max(by: { $0.1 < $1.1 })?.0
}
func firstIndexOfMinElement() -> Index? {
zip(indices, self).min(by: { $0.1 < $1.1 })?.0
}
}
Usage:
numbers.firstIndexOfMinElement() // 5
If you need to find the maximum or minimum properties:
extension Collection {
func firstIndexOfMaxElement<T: Comparable>(_ predicate: (Element) -> T) -> Index? {
zip(indices, self).max(by: { predicate($0.1) < predicate($1.1) })?.0
}
func firstIndexOfMinElement<T: Comparable>(_ predicate: (Element) -> T) -> Index? {
zip(indices, self).min(by: { predicate($0.1) < predicate($1.1) })?.0
}
}
Usage:
struct Product {
let price: Int
}
let products: [Product] = [.init(price: 2),
.init(price: 4),
.init(price: 4),
.init(price: 2),
.init(price: 3),
.init(price: 1),]
let minPrice = products.firstIndexOfMinElement(\.price) // 5
To return the maximum and minimum elements and their indices:
extension Collection where Element: Comparable {
func maxElementAndIndices() -> (indices: [Index], element: Element)? {
guard let maxValue = self.max() else { return nil }
return (indices.filter { self[$0] == maxValue }, maxValue)
}
func minElementAndIndices() -> (indices: [Index], element: Element)? {
guard let minValue = self.min() else { return nil }
return (indices.filter { self[$0] == minValue }, minValue)
}
}
And the corresponding methods to custom structures/classes:
extension Collection {
func maxElementsAndIndices<T: Comparable>(_ predicate: (Element) -> T) -> [(index: Index, element: Element)] {
guard let maxValue = self.max(by:{ predicate($0) < predicate($1)}) else { return [] }
return zip(indices, self).filter { predicate(self[$0.0]) == predicate(maxValue) }
}
func minElementsAndIndices<T: Comparable>(_ predicate: (Element) -> T) -> [(index: Index, element: Element)] {
guard let minValue = self.min(by:{ predicate($0) < predicate($1)}) else { return [] }
return zip(indices, self).filter { predicate(self[$0.0]) == predicate(minValue) }
}
}
Usage:
let maxNumbers = numbers.maxElementAndIndices() // ([1, 2], element 4)
let minNumbers = numbers.minElementAndIndices() // ([5], element 1)
let maxPriceIndices = products.maxElementsAndIndices(\.price) // [(index: 1, element: Product(price: 4)), (index: 2, element: Product(price: 4))]
let minPriceIndices = products.minElementsAndIndices(\.price) // [(index: 5, element: __lldb_expr_22.Product(price: 1))]
Upvotes: 3
Reputation: 6378
The accepted answer doesn't generalize to comparing computed values on the elements. The simplest and most efficient way to get the min/max value and index is to enumerate the list and work with the tuples (offset, element) instead:
struct Player {
let name: String
let stats: [Double]
}
let found = players.enumerated().max(by: { (a, b) in
battingAvg(a.element.stats) < battingAvg(b.element.stats)
})
print(found.element.name, found.offset) // "Joe", 42
In general you shouldn't rely on comparing floating point values by equality and even where you can, if the computation is expensive you don't want to repeat it to find the item in the list.
Upvotes: 16
Reputation: 1451
If you have 2 arrays and need to find max score from first one in order to pull the name from second one, then I would recommend you to convert both arrays into one using zip
high order func and retrieve the max value from there.
So having your data it will look like this:
let players = ["Bill", "Bob", "Sam", "Dave"]
let scores = [10,15,12,15]
let data = zip(players, scores)
// max score
let maxResult = data.max(by: ({ $0.1 < $1.1 }))?.1 ?? 0
// outputs 15
// leaders
let leaders = data.filter { $0.1 >= maxResult }.map { "\($0.0) - \($0.1)" }
// outputs ["Bob - 15", "Dave - 15"]
Upvotes: 3
Reputation: 5710
To answer just the question in the title -- find the index of the max value in a (single) array:
extension Array where Element: Comparable {
var indexOfMax: Index? {
guard var maxValue = self.first else { return nil }
var maxIndex = 0
for (index, value) in self.enumerated() {
if value > maxValue {
maxValue = value
maxIndex = index
}
}
return maxIndex
}
}
The extension returns nil if the array is empty. Else, it starts by assuming the first value is the max, iterates over all values, updates the index and value to any larger values found, and finally returns the result.
Upvotes: 7
Reputation: 72410
What you need is to use custom class
or structure
and make array of it then find max score and after that filter your array with max score.
struct Player {
let name: String
let score: Int
}
Now create array of this Player structure
var players = [Player(name: "Bill", score: 10), Player(name: "Bob", score: 15), Player(name: "Sam", score: 12), Player(name: "Dave", score: 15)]
let maxScore = players.max(by: { $0.0.score < $0.1.score })?.score ?? 0
To get the array of player with max core use filter on array like this.
let allPlayerWithMaxScore = players.filter { $0.score == maxScore }
To get the array of index for player having high score use filter on array like this.
let indexForPlayerWithMaxScore = players.indices.filter { players[$0].score == maxScore }
print(indexForPlayerWithMaxScore) //[1, 3]
Upvotes: 7
Reputation: 476
There are a couple of ways to solve your problem, you can solve this by saving the indices of scores.max() and iterate through the players list, and also using then zip function:
var max_score = scores.max()
var players_and_score = zip(players, scores)
for player in players_and_score{
if player.1 == max_score{
print(player.0)
}
}
Upvotes: 0