简体   繁体   中英

FirstIndex:of: in an array of objects

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

  1. element1 (value = 12, timestamp = 2)
  2. element2 (value = 5 , timestamp = 3)
  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?

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.

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM