Efficient algorithm for maximum value and its index in Swift array

前端 未结 2 1171
野趣味
野趣味 2021-01-20 02:54

I have written an algorithm is Swift for finding the maximum value and its index in a Swift array. This is inspired by the \"max.m\" function in Matlab & Octave.

相关标签:
2条回答
  • 2021-01-20 03:44

    Code Review on the original code

    Here are the changes I would make. All this is kind of rough (I haven't checked if all these compile), but will put you on the right track.

    1. Make the code generic

      public func max (y: [Double]) -> (Int, Double) {
      

      becomes:

      public func max<T: Comparable>(y: [T]) -> (Int, T) {
      
    2. Remove the useless parameter keyword y, and rename y to something meaningful

      public func max<T: Comparable>(_ array: [T]) -> (Int, T) {
      
    3. Add keywords to the result:

      public func max<T: Comparable>(_ array: [T]) -> (index: Int, value: T) {
      
    4. Add it as an extension on Array or RandomAccessCollection, replacing the need for the parameter entirely:

      extension Array where Element: Comparable {
          public func max() -> (index: Int, value: Element) {
              // ...
          }
      }
      
    5. Inline the unnecessary variable inLen, or at least name it better, like just count

    6. Rename out and outp to something better, such as maxValue and maxIndex

    7. Don't use yoda comparisons in Swift. = isn't an exprission in Swift, so there's no risk of it accidentally causing assignment in your if statement rather than comparison. It would trigger a compiler error. Also, omit the

      if (1 == count) {
      

      should be

      if (count == 1) {
      
    8. Omit uneeded parathesis in the if statements:

      if count == 1 {
      
    9. Rearrange the count checks in a more logical order. Rather than 1, 0, 1+, order it as 0, 1, 1+.

    10. Return early, rather than using if/elseif/else to skip chunks of code to get to a common return. Instead of:

      extension Array where Element: Comparable {
          public func max() -> (index: Int, value: Element) {
              var maxValue = Double()
              var maxIndex = Int()
      
              if count == 0 { // if no elements
                  maxValue = -1
                  maxIndex = -1
              } else if count == 1 { // if only one element
                  maxValue = self[0]
                  maxIndex = 0
              } else {
                  maxValue = self[0]
                  maxIndex = 0
                  for i in 1...inLen-1 {
                      if (maxValue < self[i]){
                          maxValue = self[i]
                          maxIndex = i
                      }
                  }
              }
              return (index: maxIndex, value: maxValue)
          }
      }
      

      try this:

      extension Array where Element: Comparable {
          public func max() -> (index: Int, value: Element)? {
              var maxValue = Double()
              var maxIndex = Int()
      
              if count == 0 { return (index: -1, value: -1) } 
              if count == 1 { return (index: 0, value: self[0]) }
      
              maxValue = self[0]
              maxIndex = 0
              for i in 1...count-1 {
                  if (maxValue < self[i]) {
                      maxValue = self[i]
                      maxIndex = i
                  }
              }
              return (index: maxIndex, value: maxValue)
          }
      }
      
    11. Now you can remove the forward declarations of maxValue and maxIndex

      extension Array where Element: Comparable {
          public func max() -> (index: Int, value: Element) {
              if count == 0 { return (index: -1, value: -1) } 
              if count == 1 { return (index: 0, value: self[0]) }
      
              var maxValue = self[0]
              var maxIndex = 0
              for i in 1...count-1 {
                  if (maxValue < self[i]) {
                      maxValue = self[i]
                      maxIndex = i
                  }
              }
              return (index: maxIndex, value: maxValue)
          }
      }
      
    12. Avoid rewriting things like 1 ... x-1 instead, use ..<: for i in 1 ..< count {

    13. In this case, it's even better to use self.indices, which achieves the same thing:

      for i in self.indicies {
      
    14. If you need both the indices, and the values assosiated with those indices, use enumerated():

      for (index, value) in self.enumerated() {
      
    15. Never use sentinel values like -1 and "" in Swift. We have optionals for expressing the absense of a value. Use them:

      extension Array where Element: Comparable {
          public func max() -> (index: Int, value: Element)? {
              if count == 0 { return nil } 
              if count == 1 { return (index: 0, value: self[0]) }
      
              var maxValue = self[0]
              var maxIndex = 0
              for (index, value) in self.enumerated() {
                  if (maxValue < value) {
                      maxValue = value
                      maxIndex = index
                  }
              }
              return (index: maxIndex, value: maxValue)
          }
      }
      
    16. I would also use tuple assignment to make a bit shorter:

      extension Array where Element: Comparable {
          public func max() -> (index: Int, value: Element)? {
              if count == 0 { return nil } 
              if count == 1 { return (index: 0, value: self[0]) }
      
              var (maxIndex, maxValue) = (0, self[0])
              for (index, value) in self.enumerated() {
                  if (maxValue < value) {
                      (maxIndex, maxValue) = (index, value)
                  }
              }
              return (index: maxIndex, value: maxValue)
          }
      }
      
    17. Now that we're using tuple assignment, we can see that we can just combine maxValue and maxIndex into a tuple, which we return directly:

      extension Array where Element: Comparable {
          public func max() -> (index: Int, value: Element)? {
              if count == 0 { return nil } 
              if count == 1 { return (index: 0, value: self[0]) }
      
              var maxElement = (index: 0, value: self[0])
              for (index, value) in self.enumerated() {
                  if (maxElement.value < value) { maxElement = (index, value) }
              }
              return maxElement
          }
      }
      

    Here's how to call the new method:

    let array: [Double] = [3, 4, 5, 6, 7, 8, 9, 100, 100, 11, 12, 13, 14, 15, -8, -7, -7, 99]
    
    if let (maxIndex, maxValue) = array.max() {
        print("The max element is \(maxValue) at index \(maxIndex)")
    }
    else {
        print("The array is empty, and has no max element or index.")
    }
    

    The idomatic Swift approach

    let array: [Double] = [3, 4, 5, 6, 7, 8, 9, 100, 100, 11, 12, 13, 14, 15, -8, -7, -7, 99]
    
    if let (maxIndex, maxValue) = array.enumerated.max{ $0.element < $1.element } {
        print("The max element is \(maxValue) at index \(maxIndex)")
    }
    else {
        print("The array is empty, and has no max element or index.")
    }
    

    MartinR's approach, improved

    Here's a wrapper around MartinR's approach, to make it easier to integrate with other Swift code:

    func max(of array: [Double]) -> (index: Int, value: Double)? {
        var maxValue = Double()
        var maxIndex = vDSP_Length()
        vDSP_maxviD(array, 1, &maxValue, &maxIndex, vDSP_Length(array.count))
    
        if maxValue == -Double.infinity { return nil }
    
        return (index: Int(maxIndex), value: maxValue)
    }
    
    0 讨论(0)
  • 2021-01-20 03:53

    You can use the vDSP_maxviD)() function from the Accelerate framework. The vDSP functions use vDSP_Length (aka UInt) for array counts and indices, so you have to convert the index to an Int for Swift interoperability.

    import Accelerate
    
    let array: [Double] = ...
    
    var elem = 0.0
    var vdspIndex: vDSP_Length = 0
    vDSP_maxviD(array, 1, &elem, &vdspIndex, vDSP_Length(array.count))
    let idx = Int(vdspIndex)
    
    print("max:", elem, "at index:", idx)
    

    This turned out to be about a factor 5 faster than your explicit loop for a 15,000 element array (on an iMac compiled in Release mode).

    0 讨论(0)
提交回复
热议问题