Reputation: 35953
I have this class:
class ValueTimestamp {
let value: Double
let timestamp : Double
init(value:Double, timestamp:Double) {
self.value = value
self.timestamp = timestamp
}
}
then I have an array of objects of this class.
Now I want to scan that array and find the object of ValueTimestamp
class with the minimum value.
Suppose the array has 3 elements
element1
(value = 12, timestamp = 2)element2
(value = 5 , timestamp = 3)element3
(value = 10, timestamp = 4)and
let myArray = [element1, element2, element3]
now I want to find the element that has the minimum value.
I supposed this would work
let min = myArray.map({$0.value}).min()
let minIndex = myArray.firstIndex(of: min)
but the second line gives me this error
Incorrect argument label in call (have 'of:', expected 'where:')
any ideas?
Upvotes: 1
Views: 3715
Reputation: 63271
The root cause here is that firstIndex(of:_)
is only defined on Collection where Element: Equatable
. Your type isn't equatable, so this method isn't available to you, until you make it conform.
However, your problem can be more elegantly solved by using Array.enumerated()
and Array.min(by:_)
:
If you only need the element, you can do this:
let timestampedValues = [element1, element2, element3]
let minTimestampedValue = timestampedValues
.enumerated()
.min(by: { $0.value })
print(minTimestampedValue as Any)
If you only need the index, you can do this:
let minTimestampedValueIndex = timestampedValues
.enumerated()
.min(by: { $0.element.value < $1.element.value })?.offset
print(minTimestampedValueIndex as Any)
If you want both, you can do this:
let minTimestampedValuePair = timestampedValues
.enumerated()
.min(by: { $0.element.value < $1.element.value })
print(minTimestampedValuePair.offset as Any, minTimestampedValuePair.element as Any)
All three of these snippets obtain the answer using only a single pass through the array, which makes them twice as fast as the "find the min, then find its index" approach.
Upvotes: 0
Reputation: 2666
firstIndex(of: )
does not work because I presume your class does not conform to Equatable
.
Thats why it is expected from you to use firstIndex(where:)
for this case.
Also in the code below you are not getting an object, you are getting the value, so min
is type of Double?
not ValueTimeStamp?
:
let min = myArray.map({$0.value}).min()
You could get the min index with the following with using where:
let minIndex = myArray.firstIndex(where: {$0.value == min})
References:
https://developer.apple.com/documentation/swift/array/2994720-firstindex https://developer.apple.com/documentation/swift/array/2994722-firstindex
Upvotes: 7
Reputation: 7096
firstIndex:of:
looks for the first element that is equal to the provided argument. But you aren't looking for an element that's equal to it, you're looking for one whose value
property is equal. So you need to use where
and provide a function for that instead:
let minIndex = myArray.firstIndex(where: {$0.value == min})
You could also make your class conform to Comparable
and call min
on it directly:
class ValueTimestamp: Comparable {
let value: Double
let timestamp : Double
init(value:Double, timestamp:Double) {
self.value = value
self.timestamp = timestamp
}
static func == (lhs: ValueTimestamp, rhs: ValueTimestamp) -> Bool {
return lhs.value == rhs.value
}
static func < (lhs: ValueTimestamp, rhs: ValueTimestamp) -> Bool {
return lhs.value < rhs.value
}
}
let minObject = myArray.min()
Note that if it's possible to have two objects with the same value
, you may need to adjust the functions to determine which one is "less" in that case.
Upvotes: 5