【问题标题】:How can I extend typed Arrays in Swift?如何在 Swift 中扩展类型化数组?
【发布时间】:2014-07-24 11:51:39
【问题描述】:

如何使用自定义功能实用程序扩展 Swift 的 Array<T>T[] 类型?

浏览 Swift 的 API 文档表明 Array 方法是 T[] 的扩展,例如:

extension T[] : ArrayType {
    //...
    init()

    var count: Int { get }

    var capacity: Int { get }

    var isEmpty: Bool { get }

    func copy() -> T[]
}

当复制和粘贴相同的源并尝试任何变体时:

extension T[] : ArrayType {
    func foo(){}
}

extension T[] {
    func foo(){}
}

构建失败并出现错误:

标称类型T[]不能扩展

使用完整类型定义失败,Use of undefined type 'T',即:

extension Array<T> {
    func foo(){}
}

它也因Array&lt;T : Any&gt;Array&lt;String&gt; 而失败。

奇怪的是 Swift 让我扩展了一个无类型数组:

extension Array {
    func each(fn: (Any) -> ()) {
        for i in self {
            fn(i)
        }
    }
}

它让我打电话:

[1,2,3].each(println)

但我无法创建适当的泛型类型扩展,因为类型在流经方法时似乎丢失了,例如尝试replace Swift's built-in filter with:

extension Array {
    func find<T>(fn: (T) -> Bool) -> T[] {
        var to = T[]()
        for x in self {
            let t = x as T
            if fn(t) {
                to += t
            }
        }
        return to
    }
}

但编译器将其视为无类型,它仍然允许使用以下方式调用扩展:

["A","B","C"].find { $0 > "A" }

当使用调试器逐步指示类型为 Swift.String 但尝试像字符串一样访问它而不首先将其转换为 String 时会出现构建错误,即:

["A","B","C"].find { ($0 as String).compare("A") > 0 }

有谁知道创建类似于内置扩展的类型化扩展方法的正确方法是什么?

【问题讨论】:

  • 投了赞成票,因为我自己也找不到答案。在 XCode 中单击 Array 类型时看到相同的extension T[] 位,但没有看到任何方法来实现它而不会出错。
  • @usernametbd FYI 刚刚找到它,看起来解决方案是从方法签名中删除 &lt;T&gt;

标签: arrays swift


【解决方案1】:

扩展所有类型:

extension Array where Element: Any {
    // ...
}

扩展可比较类型:

extension Array where Element: Comparable {
    // ...
}

扩展一些类型:

extension Array where Element: Comparable & Hashable {
    // ...
}

扩展一个特定的类型:

extension Array where Element == Int {
    // ...
}

【讨论】:

    【解决方案2】:

    对于使用 classes 扩展类型化数组,以下内容适用于我(Swift 2.2)。例如,对类型化数组进行排序:

    class HighScoreEntry {
        let score:Int
    }
    
    extension Array where Element == HighScoreEntry {
        func sort() -> [HighScoreEntry] {
          return sort { $0.score < $1.score }
        }
    }
    

    尝试使用 structtypealias 执行此操作会出错:

    Type 'Element' constrained to a non-protocol type 'HighScoreEntry'
    

    更新

    要使用非类扩展类型化数组,请使用以下方法:

    typealias HighScoreEntry = (Int)
    
    extension SequenceType where Generator.Element == HighScoreEntry {
        func sort() -> [HighScoreEntry] {
          return sort { $0 < $1 }
        }
    }
    

    Swift 3 中,一些类型已被重命名:

    extension Sequence where Iterator.Element == HighScoreEntry 
    {
        // ...
    }
    

    【讨论】:

    • 编译器报告“SequenceType”已重命名为“Sequence”
    • 为什么你没有在返回类型[Iterator.Element]中使用Iterator.Element?
    • 嗨,您能解释一下 4.1 中的条件一致性功能吗? 4.1 有什么新功能?我们可以在 2.2 中做到这一点吗?我错过了什么
    • 从 Swift 3.1 开始,您可以使用以下语法扩展具有非类的数组:extension Array where Element == Int
    【解决方案3】:

    如果您想了解扩展数组和其他类型的内置类检查代码,请查看此 github 存储库 https://github.com/ankurp/Cent

    从 Xcode 6.1 开始,扩展数组的语法如下

    extension Array {
        func at(indexes: Int...) -> [Element] {
            ... // You code goes herer
        }
    }
    

    【讨论】:

    • @Rob 更新了网址
    【解决方案4】:

    使用 Swift 2.2: 尝试从字符串数组中删除重复项时遇到了类似的问题。我能够在 Array 类上添加一个扩展,这正是我想要做的。

    extension Array where Element: Hashable {
        /**
         * Remove duplicate elements from an array
         *
         * - returns: A new array without duplicates
         */
        func removeDuplicates() -> [Element] {
            var result: [Element] = []
            for value in self {
                if !result.contains(value) {
                    result.append(value)
                }
            }
            return result
        }
    
        /**
         * Remove duplicate elements from an array
         */
        mutating func removeDuplicatesInPlace() {
            var result: [Element] = []
            for value in self {
                if !result.contains(value) {
                    result.append(value)
                }
            }
            self = result
        }
    }
    

    将这两个方法添加到 Array 类允许我调用数组上的两个方法之一并成功删除重复项。请注意,数组中的元素必须符合 Hashable 协议。现在我可以这样做了:

     var dupes = ["one", "two", "two", "three"]
     let deDuped = dupes.removeDuplicates()
     dupes.removeDuplicatesInPlace()
     // result: ["one", "two", "three"]
    

    【讨论】:

    • 这也可以使用let deDuped = Set(dupes) 来完成,只要您对类型更改没问题,您就可以通过称为toSet 的非破坏性方法返回它
    • @alexpyoung 如果你执行 Set() 会搞乱数组的顺序
    【解决方案5】:
    import Foundation
    
    extension Array {
        var randomItem: Element? {
            let idx = Int(arc4random_uniform(UInt32(self.count)))
            return self.isEmpty ? nil : self[idx]
        }
    }
    

    【讨论】:

      【解决方案6】:

      Swift 2.x

      您还可以扩展数组以符合包含用于泛型类型方法的 blue-rpints 的协议,例如,包含您的自定义功能实用程序的协议,用于符合某种类型约束的所有泛型数组元素,例如协议 MyTypes。使用这种方法的好处是您可以编写带有通用数组参数的函数,但这些数组参数必须符合您的自定义函数实用程序协议,例如协议MyFunctionalUtils

      您可以通过将数组元素类型约束为MyTypes 来隐式地获得这种行为,或者——正如我将在下面描述的方法中展示的那样——非常简洁、明确地让你的泛型数组函数header 直接表明输入数组符合MyFunctionalUtils


      我们从协议MyTypes 开始,用作类型约束;通过此协议扩展您希望适合泛型的类型(下面的示例扩展了基本类型 IntDouble 以及自定义类型 MyCustomType

      /* Used as type constraint for Generator.Element */
      protocol MyTypes {
          var intValue: Int { get }
          init(_ value: Int)
          func *(lhs: Self, rhs: Self) -> Self
          func +=(inout lhs: Self, rhs: Self)
      }
      
      extension Int : MyTypes { var intValue: Int { return self } }
      extension Double : MyTypes { var intValue: Int { return Int(self) } }
          // ...
      
      /* Custom type conforming to MyTypes type constraint */
      struct MyCustomType : MyTypes {
          var myInt : Int? = 0
          var intValue: Int {
              return myInt ?? 0
          }
      
          init(_ value: Int) {
              myInt = value
          }
      }
      
      func *(lhs: MyCustomType, rhs: MyCustomType) -> MyCustomType {
          return MyCustomType(lhs.intValue * rhs.intValue)
      }
      
      func +=(inout lhs: MyCustomType, rhs: MyCustomType) {
          lhs.myInt = (lhs.myInt ?? 0) + (rhs.myInt ?? 0)
      }
      

      Protocol MyFunctionalUtils(包含我们的其他通用数组函数实用程序的蓝图)以及此后由MyFunctionalUtils 对 Array 的扩展;蓝图方法的实现:

      /* Protocol holding our function utilities, to be used as extension 
         o Array: blueprints for utility methods where Generator.Element 
         is constrained to MyTypes */
      protocol MyFunctionalUtils {
          func foo<T: MyTypes>(a: [T]) -> Int?
              // ...
      }
      
      /* Extend array by protocol MyFunctionalUtils and implement blue-prints 
         therein for conformance */
      extension Array : MyFunctionalUtils {
          func foo<T: MyTypes>(a: [T]) -> Int? {
              /* [T] is Self? proceed, otherwise return nil */
              if let b = self.first {
                  if b is T && self.count == a.count {
                      var myMultSum: T = T(0)
      
                      for (i, sElem) in self.enumerate() {
                          myMultSum += (sElem as! T) * a[i]
                      }
                      return myMultSum.intValue
                  }
              }
              return nil
          }
      }
      

      最后,测试和两个例子展示了一个使用泛型数组的函数,分别有以下情况

      1. 通过将数组元素类型约束为“MyTypes”(函数bar1),显示数组参数符合协议“MyFunctionalUtils”的隐式断言。

        李>
      2. 显示明确数组参数符合协议'MyFunctionalUtils'(函数bar2)。

      测试和示例如下:

      /* Tests & examples */
      let arr1d : [Double] = [1.0, 2.0, 3.0]
      let arr2d : [Double] = [-3.0, -2.0, 1.0]
      
      let arr1my : [MyCustomType] = [MyCustomType(1), MyCustomType(2), MyCustomType(3)]
      let arr2my : [MyCustomType] = [MyCustomType(-3), MyCustomType(-2), MyCustomType(1)]
      
          /* constrain array elements to MyTypes, hence _implicitly_ constraining
             array parameters to protocol MyFunctionalUtils. However, this
             conformance is not apparent just by looking at the function signature... */
      func bar1<U: MyTypes> (arr1: [U], _ arr2: [U]) -> Int? {
          return arr1.foo(arr2)
      }
      let myInt1d = bar1(arr1d, arr2d) // -4, OK
      let myInt1my = bar1(arr1my, arr2my) // -4, OK
      
          /* constrain the array itself to protocol MyFunctionalUtils; here, we
             see directly in the function signature that conformance to
             MyFunctionalUtils is given for valid array parameters */
      func bar2<T: MyTypes, U: protocol<MyFunctionalUtils, _ArrayType> where U.Generator.Element == T> (arr1: U, _ arr2: U) -> Int? {
      
          // OK, type U behaves as array type with elements T (=MyTypes)
          var a = arr1
          var b = arr2
          a.append(T(2)) // add 2*7 to multsum
          b.append(T(7))
      
          return a.foo(Array(b))
              /* Ok! */
      }
      let myInt2d = bar2(arr1d, arr2d) // 10, OK
      let myInt2my = bar2(arr1my, arr2my) // 10, OK
      

      【讨论】:

        【解决方案7】:

        我有一个类似的问题 - 想用一个 swap() 方法扩展通用数组,该方法应该采用与数组相同类型的参数。但是如何指定泛型类型?我通过反复试验发现以下方法有效:

        extension Array {
            mutating func swap(x:[Element]) {
                self.removeAll()
                self.appendContentsOf(x)
            }
        }
        

        它的关键是“元素”这个词。请注意,我没有在任何地方定义此类型,它似乎自动存在于数组扩展的上下文中,并引用数组元素的任何类型。

        我不是 100% 确定那里发生了什么,但我认为这可能是因为“元素”是数组的关联类型(请参阅此处的“关联类型”https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Generics.html#//apple_ref/doc/uid/TP40014097-CH26-ID189

        但是,我在 Array 结构参考 (https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_Array_Structure/index.html#//apple_ref/swift/struct/s:Sa) 中看不到对此的任何参考...所以我还是有点不确定。

        【讨论】:

        • Array 是泛型类型:Array&lt;Element&gt;(参见swiftdoc.org/v2.1/type/Array),Element 是包含类型的占位符。例如:var myArray = [Foo]() 表示myArray 将只包含类型Foo。在这种情况下,Foo 被“映射”到通用占位符 Element。如果您想更改 Array 的一般行为(通过扩展),您将使用通用占位符 Element 而不是任何具体类型(如 Foo)。
        【解决方案8】:
        import Foundation
        
        extension Array {
        
            func calculateMean() -> Double {
                // is this an array of Doubles?
                if self.first is Double {
                    // cast from "generic" array to typed array of Doubles
                    let doubleArray = self.map { $0 as! Double }
        
                    // use Swift "reduce" function to add all values together
                    let total = doubleArray.reduce(0.0, combine: {$0 + $1})
        
                    let meanAvg = total / Double(self.count)
                    return meanAvg
        
                } else {
                    return Double.NaN
                }
            }
        
            func calculateMedian() -> Double {
                // is this an array of Doubles?
                if self.first is Double {
                    // cast from "generic" array to typed array of Doubles
                    var doubleArray = self.map { $0 as! Double }
        
                    // sort the array
                    doubleArray.sort( {$0 < $1} )
        
                    var medianAvg : Double
                    if doubleArray.count % 2 == 0 {
                        // if even number of elements - then mean average the middle two elements
                        var halfway = doubleArray.count / 2
                        medianAvg = (doubleArray[halfway] + doubleArray[halfway - 1]) / 2
        
                    } else {
                        // odd number of elements - then just use the middle element
                        medianAvg = doubleArray[doubleArray.count  / 2 ]
                    }
                    return medianAvg
                } else {
                    return Double.NaN
                }
        
            }
        
        }
        

        【讨论】:

        • 在我看来,这些向下转换 ($0 as! Double) 正在与 Swift 的类型系统作斗争,也违背了 OP 问题的目的。通过这样做,您将失去对您实际想要执行的计算进行编译器优化的任何潜力,并且您还用无意义的函数污染了 Array 的命名空间(为什么要在 UIViews 数组中看到 .calculateMedian() ,或者除了 Double 之外的任何东西?)。有更好的方法。
        • 试试extension CollectionType where Generator.Element == Double {}
        【解决方案9】:

        我查看了 Swift 2 标准库的头文件,这里是过滤器函数的原型,这使得如何滚动你自己的函数变得非常明显。

        extension CollectionType {
            func filter(@noescape includeElement: (Self.Generator.Element) -> Bool) -> [Self.Generator.Element]
        }
        

        它不是对 Array 的扩展,而是对 CollectionType 的扩展,因此同样的方法适用于其他集合类型。 @noescape 表示传入的块不会离开过滤器功能的范围,这可以进行一些优化。带有大写字母 S 的 Self 是我们正在扩展的类。 Self.Generator 是一个迭代器,它遍历集合中的对象,而 Self.Generator.Element 是对象的类型,例如对于数组 [Int?] Self.Generator.Element 将是 Int?。

        总而言之,这个过滤器方法可以应用于任何CollectionType,它需要一个过滤器块,它接受集合的一个元素并返回一个Bool,它返回一个原始类型的数组。所以把这些放在一起,这是一个我觉得有用的方法:它结合了 map 和 filter,通过获取一个将集合元素映射到可选值的块,并返回一个由非 nil 的可选值组成的数组。

        extension CollectionType {
        
            func mapfilter<T>(@noescape transform: (Self.Generator.Element) -> T?) -> [T] {
                var result: [T] = []
                for x in self {
                    if let t = transform (x) {
                        result.append (t)
                    }
                }
                return result
            }
        }
        

        【讨论】:

          【解决方案10】:

          尝试了一段时间后,解决方案似乎从签名中删除了&lt;T&gt;,例如:

          extension Array {
              func find(fn: (T) -> Bool) -> [T] {
                  var to = [T]()
                  for x in self {
                      let t = x as T;
                      if fn(t) {
                          to += t
                      }
                  }
                  return to
              }
          }
          

          现在可以按预期工作而不会出现构建错误:

          ["A","B","C"].find { $0.compare("A") > 0 }
          

          【讨论】:

          • 顺便说一句,您在此处定义的内容在功能上等同于现有的 filter 函数:let x = ["A","B","C","X”].filter { $0.compare("A") &gt; 0 }
          • @Palimondo 不,不是,the built-in filter executes callbacks twice
          • 我明白了。双重过滤对我来说似乎相当麻烦......但它仍然认为filter 与您的find 功能等效,即函数的结果是相同的。如果您的过滤器关闭有副作用,那么您肯定会不喜欢结果。
          • @Palimondo 没错,默认过滤器有意外行为,而上面的 find impl 按预期工作(以及它存在的原因)。如果它执行两次闭包,它在功能上并不等效,这可能会改变作用域变量(这恰好是我遇到的错误,因此对其行为提出了问题)。还要注意这个问题特别提到想要替换 Swift 的内置 filter
          • 我们似乎在争论functional这个词的定义。通常,在 filtermapreduce 函数源自的函数式编程范例中,函数会针对它们的返回值执行。相比之下,您在上面定义的 each 函数是为其副作用执行的函数的示例,因为它不返回任何内容。我想我们可以同意当前的 Swift 实现并不理想,并且文档没有说明它的运行时特性。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-02-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-05-18
          相关资源
          最近更新 更多