Làm cách nào tôi có thể mở rộng Mảng đã gõ trong Swift?


203

Làm cách nào tôi có thể mở rộng Swift Array<T>hoặc T[]gõ với các tiện ích chức năng tùy chỉnh?

Duyệt qua các tài liệu API của Swift cho thấy các phương thức Array là một phần mở rộng của T[], ví dụ:

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

    var count: Int { get }

    var capacity: Int { get }

    var isEmpty: Bool { get }

    func copy() -> T[]
}

Khi sao chép và dán cùng một nguồn và thử bất kỳ biến thể nào như:

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

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

Không thể xây dựng với lỗi:

Loại danh nghĩa T[]không thể được mở rộng

Sử dụng định nghĩa kiểu đầy đủ không thành công với Use of undefined type 'T', tức là:

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

Và nó cũng thất bại với Array<T : Any>Array<String>.

Thật kỳ lạ Swift cho phép tôi mở rộng một mảng chưa được đánh dấu bằng:

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

Mà nó cho phép tôi gọi với:

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

Nhưng tôi không thể tạo tiện ích mở rộng loại chung thích hợp vì loại dường như bị mất khi nó chảy qua phương thức, ví dụ: cố gắng thay thế bộ lọc tích hợp của Swift bằng :

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
    }
}

Nhưng trình biên dịch coi nó như được tháo ra khi nó vẫn cho phép gọi phần mở rộng bằng:

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

Và khi bước qua với trình gỡ lỗi cho biết loại đó là lỗi Swift.Stringnhưng đó là lỗi xây dựng để thử truy cập nó như Chuỗi mà không chuyển nó sang Stringđầu tiên, nghĩa là:

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

Có ai biết cách thích hợp để tạo một phương thức tiện ích mở rộng được nhập giống như các tiện ích mở rộng tích hợp không?


Bỏ phiếu vì tôi cũng không thể tìm thấy câu trả lời. Thấy cùng một extension T[]bit khi nhấp vào Lệnh trên loại Mảng trong XCode, nhưng không thấy bất kỳ cách nào để thực hiện nó mà không gặp lỗi.
tên người dùng tbd

@usernametbd FYI vừa tìm thấy nó, có vẻ như giải pháp là xóa <T>khỏi chữ ký phương thức.
huyền thoại

Câu trả lời:


296

Để mở rộng các mảng được gõ với các lớp , bên dưới hoạt động với tôi (Swift 2.2 ). Ví dụ: sắp xếp một mảng đã gõ:

class HighScoreEntry {
    let score:Int
}

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

Cố gắng làm điều này với một cấu trúc hoặc typealias sẽ đưa ra một lỗi:

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

Cập nhật :

Để mở rộng các mảng được gõ với các lớp không sử dụng cách tiếp cận sau:

typealias HighScoreEntry = (Int)

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

Trong Swift 3, một số loại đã được đổi tên:

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

1
trình biên dịch báo cáo rằng 'SequenceType' đã được đổi tên thành 'Sequence'
sandover

Tại sao bạn không sử dụng Iterator.Euity trong kiểu trả về [Iterator.Element]?
gaussblurinc

1
xin chào, bạn có thể giải thích tính năng Phù hợp có điều kiện trong 4.1 không? Có gì mới trong 4.1? Chúng ta có thể làm điều đó trong 2.2? Tôi đang thiếu gì
osrl

Kể từ Swift 3.1, bạn có thể mở rộng các mảng với các lớp không theo cú pháp sau: Mảng mở rộng trong đó Element == Int
Giles

63

Sau một thời gian thử những thứ khác nhau, giải pháp dường như loại bỏ <T>chữ ký như:

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
    }
}

Mà bây giờ hoạt động như dự định mà không có lỗi xây dựng:

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

1
BTW Những gì bạn đã xác định ở đây tương đương về filterchức năng với chức năng hiện có :let x = ["A","B","C","X”].filter { $0.compare("A") > 0 }
Palimondo


4
Tôi hiểu rồi. Lọc kép có vẻ khá buggy với tôi ... Nhưng nó vẫn chủ trương rằng filterchức năng tương đương để bạn find, tức là kết quả của hàm là như nhau. Nếu đóng bộ lọc của bạn có tác dụng phụ, chắc chắn bạn có thể không thích kết quả.
Palimondo

2
@Palimondo Chính xác, bộ lọc mặc định có hành vi không mong muốn trong khi công cụ tìm kiếm ở trên hoạt động như mong đợi (và tại sao nó tồn tại). Nó không tương đương về mặt chức năng nếu nó thực hiện việc đóng hai lần, có khả năng biến đổi các biến trong phạm vi (đó là lỗi tôi gặp phải, do đó là câu hỏi về hành vi của nó). Cũng lưu ý câu hỏi đề cập cụ thể muốn thay thế tích hợp sẵn của Swift filter.
huyền thoại

4
Chúng tôi dường như đang tranh cãi về định nghĩa của chức năng từ . Phong tục, trong mô hình lập trình chức năng nơi filter, mapreducechức năng có nguồn gốc từ, chức năng được thực hiện cho các giá trị trở lại của họ. Ngược lại, eachhàm bạn định nghĩa ở trên là một ví dụ về hàm được thực thi cho hiệu ứng phụ của nó, bởi vì nó không trả về gì cả. Tôi đoán chúng ta có thể đồng ý rằng việc triển khai Swift hiện tại không lý tưởng và tài liệu không nêu bất cứ điều gì về đặc điểm thời gian chạy của nó.
Palimondo

24

Mở rộng tất cả các loại:

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

Mở rộng một số loại:

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

Mở rộng một loại cụ thể :

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

8

Tôi đã có một vấn đề tương tự - muốn mở rộng Mảng chung bằng phương thức exchange (), được cho là lấy một đối số cùng loại với mảng. Nhưng làm thế nào để bạn xác định loại chung? Tôi đã tìm thấy bằng cách dùng thử và lỗi mà bên dưới hoạt động:

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

Chìa khóa của nó là từ 'Element'. Lưu ý rằng tôi đã không xác định loại này ở bất cứ đâu, nó dường như tự động tồn tại trong ngữ cảnh của phần mở rộng mảng và tham khảo bất kỳ loại phần tử nào của mảng.

Tôi không chắc chắn 100% những gì đang diễn ra ở đó, nhưng tôi nghĩ có lẽ là do 'Element' là một loại liên kết của Mảng (xem 'Các loại liên kết' tại đây https://developer.apple.com/l Library / ios / documentation /Swift/Conceptual/Swift_Programming_Lingu/Generics.html#//apple_Vf/doc/uid/TP40014097-CH26-ID189 )

Tuy nhiên, tôi không thể thấy bất kỳ tham chiếu nào về điều này trong tài liệu tham khảo cấu trúc Mảng ( https://developer.apple.com/l Library / prelelease / ios / document / Swift / Reference / Swift_Array_Str struct / index.html # //apple_Vf / swift / struct / s: Sa ) ... vì vậy tôi vẫn không chắc chắn.


1
Arraylà một loại chung: Array<Element>(xem swiftdoc.org/v2.1/type/Array ), Elementlà một giữ chỗ cho loại được chứa. Ví dụ: var myArray = [Foo]()có nghĩa là myArraysẽ chỉ chứa loại Foo. Footrong trường hợp này là "ánh xạ" tới trình giữ chỗ chung Element. Nếu bạn muốn thay đổi hành vi chung của Mảng (thông qua tiện ích mở rộng), bạn sẽ sử dụng trình giữ chỗ chung Elementchứ không phải bất kỳ loại cụ thể nào (như Foo).
David James

5

Sử dụng Swift 2.2 : Tôi gặp phải một vấn đề tương tự khi cố gắng loại bỏ các bản sao khỏi một chuỗi các chuỗi. Tôi đã có thể thêm một phần mở rộng trên lớp Array, thứ thực hiện đúng như những gì tôi đang tìm kiếm.

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
    }
}

Việc thêm hai phương thức này vào lớp Array cho phép tôi gọi một trong hai phương thức trên một mảng và loại bỏ thành công các bản sao. Lưu ý rằng các phần tử trong mảng phải tuân theo giao thức Hashable. Bây giờ tôi có thể làm điều này:

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

Điều này cũng có thể được thực hiện bằng let deDuped = Set(dupes), mà bạn có thể quay lại theo phương thức không phá hủy được gọi toSetlà miễn là bạn ổn với thay đổi loại
alexpyoung

@alexpyoung bạn sẽ làm xáo trộn thứ tự của mảng nếu bạn thực hiện Set ()
Daniel Wang

5

Nếu bạn muốn tìm hiểu về việc mở rộng Mảng và các loại xây dựng khác trong mã kiểm tra lớp trong repo github này https://github.com/ankurp/Cent

Kể từ Xcode 6.1, cú pháp mở rộng mảng như sau

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

1
@Rob Cập nhật URL
Encore PTL

3

Tôi đã xem xét các tiêu đề thư viện tiêu chuẩn Swift 2, và đây là nguyên mẫu cho chức năng bộ lọc, điều này làm cho nó khá rõ ràng làm thế nào để cuộn của riêng bạn.

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

Đây không phải là một phần mở rộng cho Array, mà là CollectionType, vì vậy phương thức tương tự áp dụng cho các loại bộ sưu tập khác. @noescape có nghĩa là khối được truyền vào sẽ không rời khỏi phạm vi của chức năng lọc, cho phép một số tối ưu hóa. Tự với một thủ đô S là lớp chúng tôi đang mở rộng. Self.Generator là một trình vòng lặp lặp qua các đối tượng trong bộ sưu tập và Self.Generator.Euity là loại đối tượng, ví dụ cho một mảng [Int?] Self.Generator.Euity sẽ là Int?.

Tất cả trong tất cả phương thức bộ lọc này có thể được áp dụng cho bất kỳ CollectionType nào, nó cần một khối bộ lọc lấy một phần tử của bộ sưu tập và trả về Bool, và nó trả về một mảng của kiểu ban đầu. Vì vậy, kết hợp điều này với nhau, đây là một phương pháp mà tôi thấy hữu ích: Nó kết hợp bản đồ và bộ lọc, bằng cách lấy một khối ánh xạ phần tử bộ sưu tập thành một giá trị tùy chọn và trả về một mảng các giá trị tùy chọn không phải là không.

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
    }
}

2
import Foundation

extension Array {
    var randomItem: Element? {
        let idx = Int(arc4random_uniform(UInt32(self.count)))
        return self.isEmpty ? nil : self[idx]
    }
}

0

( Swift 2.x )

Bạn cũng có thể mở rộng mảng để tuân thủ giao thức chứa màu xanh lam cho các phương thức kiểu chung, ví dụ: giao thức chứa các tiện ích chức năng tùy chỉnh của bạn cho tất cả các thành phần mảng chung tuân theo một số ràng buộc kiểu, giao thức nói MyTypes. Phần thưởng khi sử dụng phương pháp này là bạn có thể viết các hàm lấy các đối số mảng chung, với một ràng buộc là các đối số mảng này phải tuân theo giao thức tiện ích chức năng tùy chỉnh của bạn, giao thức nóiMyFunctionalUtils .

Bạn có thể nhận được hành vi này một cách ngầm định, bằng cách gõ ràng buộc các phần tử mảng MyTypes, hoặc --- như tôi sẽ trình bày trong phương thức tôi mô tả bên dưới ---, khá gọn gàng, rõ ràng, để tiêu đề hàm chung chung của bạn hiển thị trực tiếp các mảng đầu vào phù hợp với MyFunctionalUtils.


Chúng tôi bắt đầu với các Giao thức MyTypesđể sử dụng như ràng buộc kiểu; mở rộng các loại bạn muốn phù hợp với khái quát của bạn bằng giao thức này (ví dụ bên dưới mở rộng các loại cơ bản IntDoublecũng như một loại tùy chỉnh 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)
}

Giao thức MyFunctionalUtils(giữ bản thiết kế chi tiết các tiện ích chức năng mảng chung bổ sung của chúng tôi) và sau đó, phần mở rộng của Array bằng MyFunctionalUtils; thực hiện phương pháp in màu xanh:

/* 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
    }
}

Cuối cùng, các thử nghiệm và hai ví dụ cho thấy một hàm lấy các mảng chung, với các trường hợp sau, tương ứng

  1. Hiển thị xác nhận ngầm định rằng các tham số mảng phù hợp với giao thức 'MyFeftalUtils', thông qua kiểu ràng buộc các thành phần mảng thành 'MyTypes' (hàm bar1).

  2. Hiển thị rõ ràng rằng các tham số mảng tuân thủ giao thức 'MyFactoralUtils' (chức năng bar2).

Bài kiểm tra và ví dụ sau:

/* 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

-1
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
        }

    }

}

2
$0 as! DoubleTheo ý kiến ​​của tôi, những chương trình phát sóng này ( ) đang chiến đấu chống lại hệ thống loại của Swift và cũng đánh bại mục đích của câu hỏi của OP. Bằng cách đó, bạn sẽ mất bất kỳ tiềm năng nào để tối ưu hóa trình biên dịch cho các tính toán bạn thực sự muốn và bạn cũng đang làm ô nhiễm không gian tên của Array với các hàm vô nghĩa (tại sao bạn muốn thấy .calculateMedian () trong một mảng UIViews hoặc bất cứ điều gì ngoài Double cho vấn đề đó?). Có một cách tốt hơn.
phù du

thửextension CollectionType where Generator.Element == Double {}
phù du
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.