Bagaimana saya bisa memperpanjang Array yang diketik dalam Swift?

203

Bagaimana saya bisa memperpanjang Swift Array<T>atau T[]tipe dengan utilitas fungsional kustom?

Menjelajahi dokumen API Swift menunjukkan bahwa metode array adalah perpanjangan dari T[], misalnya:

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

    var count: Int { get }

    var capacity: Int { get }

    var isEmpty: Bool { get }

    func copy() -> T[]
}

Saat menyalin dan menempel sumber yang sama dan mencoba variasi seperti:

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

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

Gagal membangun dengan kesalahan:

Jenis nominal T[]tidak dapat diperpanjang

Menggunakan definisi tipe lengkap gagal dengan Use of undefined type 'T', yaitu:

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

Dan itu juga gagal dengan Array<T : Any>dan Array<String>.

Curiously Swift memungkinkan saya memperluas array yang tidak diketik dengan:

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

Yang memungkinkan saya menelepon dengan:

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

Tapi saya tidak bisa membuat ekstensi tipe generik yang tepat karena jenisnya sepertinya hilang ketika mengalir melalui metode, misalnya mencoba mengganti filter bawaan Swift dengan :

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

Tetapi kompiler memperlakukannya sebagai tidak diketik di mana ia masih memungkinkan memanggil ekstensi dengan:

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

Dan ketika melangkah-melalui debugger menunjukkan jenisnya Swift.Stringtetapi itu adalah kesalahan build untuk mencoba mengaksesnya seperti sebuah String tanpa melemparkannya Stringterlebih dahulu, yaitu:

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

Adakah yang tahu apa cara yang tepat untuk membuat metode ekstensi yang diketik yang bertindak seperti ekstensi bawaan?

mitos
sumber
Terpilih karena saya juga tidak dapat menemukan jawaban. Melihat extension T[]bit yang sama ketika mengklik perintah pada jenis Array di XCode, tetapi tidak melihat cara untuk mengimplementasikannya tanpa mendapatkan kesalahan.
nama pengguna tbd
@ usernametbd FYI baru saja menemukannya, sepertinya solusinya adalah menghapus <T>dari metode tanda tangan.
mythz

Jawaban:

296

Untuk memperluas array yang diketik dengan kelas , di bawah ini berfungsi untuk saya (Swift 2.2 ). Misalnya, mengurutkan array yang diketik:

class HighScoreEntry {
    let score:Int
}

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

Mencoba melakukan ini dengan struct atau typealias akan memberikan kesalahan:

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

Memperbarui :

Untuk memperluas array yang diketik dengan non-kelas gunakan pendekatan berikut:

typealias HighScoreEntry = (Int)

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

Di Swift 3 beberapa jenis telah diganti namanya:

extension Sequence where Iterator.Element == HighScoreEntry 
{
    // ...
}
Andrew Schreiber
sumber
1
kompiler melaporkan bahwa 'SequenceType' telah diubah namanya menjadi 'Sequence'
sandover
Mengapa Anda tidak menggunakan Iterator. Jenis tambahan [Iterator.Element]?
gaussblurinc
1
hai, dapatkah Anda menjelaskan fitur Kesesuaian Bersyarat di 4.1? Apa yang baru di 4.1? Kita bisa melakukannya di 2.2? Apa yang saya lewatkan
osrl
Sejak Swift 3.1 Anda dapat memperluas array dengan non-kelas dengan sintaks berikut: extension Array mana Element == Int
Giles
63

Setelah beberapa saat mencoba hal yang berbeda, solusinya tampaknya menghapus <T>dari tanda tangan seperti:

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

Yang sekarang berfungsi seperti yang dimaksudkan tanpa kesalahan build:

["A","B","C"].find { $0.compare("A") > 0 }
mitos
sumber
1
BTW Apa yang telah Anda tetapkan di sini secara fungsional setara dengan filterfungsi yang ada :let x = ["A","B","C","X”].filter { $0.compare("A") > 0 }
Palimondo
5
@Palimondo Tidak, filter bawaan mengeksekusi callback dua kali .
mythz
4
Saya melihat. Pemfilteran ganda tampaknya agak membingungkan bagi saya ... Tetapi masih berpendapat bahwa filtersecara fungsional setara dengan Anda find, yaitu hasil dari fungsinya sama. Jika penutupan filter Anda memiliki efek samping, Anda mungkin tidak suka hasilnya, pasti.
Palimondo
2
@Palimondo Persis, filter default memiliki perilaku yang tidak terduga sedangkan imp penemuan di atas berfungsi seperti yang diharapkan (dan mengapa ada). Ini tidak setara secara fungsional jika mengeksekusi penutupan dua kali, yang berpotensi dapat mengubah variabel scoped (yang kebetulan adalah bug yang saya temui, maka pertanyaan tentang perilakunya). Perhatikan juga pertanyaan yang secara spesifik menyebutkan keinginan untuk menggantikan built-in Swift filter.
mythz
4
Kami tampaknya berdebat tentang definisi kata fungsional . Biasanya, dalam paradigma pemrograman fungsional di mana filter, mapdan reducefungsi berasal, fungsi dieksekusi untuk nilai pengembaliannya. Sebaliknya, eachfungsi yang Anda tetapkan di atas adalah contoh dari fungsi yang dieksekusi untuk efek sampingnya, karena tidak menghasilkan apa-apa. Saya kira kita bisa sepakat bahwa implementasi Swift saat ini tidak ideal dan dokumentasi tidak menyatakan apa-apa tentang karakteristik runtime-nya.
Palimondo
24

Perpanjang semua jenis:

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

Perpanjang beberapa jenis:

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

Perpanjang jenis tertentu :

extension Array where Element == Int {
    // ...
}
Dmitry
sumber
8

Saya memiliki masalah yang sama - ingin memperpanjang Array umum dengan metode swap (), yang seharusnya mengambil argumen dengan tipe yang sama dengan array. Tetapi bagaimana Anda menentukan jenis generik? Saya menemukan dengan coba-coba bahwa di bawah ini berfungsi:

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

Kunci untuk itu adalah kata 'Elemen'. Perhatikan bahwa saya tidak mendefinisikan jenis ini di mana pun, sepertinya ada secara otomatis dalam konteks ekstensi array, dan merujuk pada apa pun jenis elemen array.

Saya tidak 100% yakin apa yang terjadi di sana, tapi saya pikir itu mungkin karena 'Elemen' adalah tipe terkait dari Array (lihat 'Jenis Terkait' di sini https://developer.apple.com/library/ios/documentation /Swift/Conceptual/Swift_Programming_Language/Generics.html#//apple_ref/doc/uid/TP40014097-CH26-ID189 )

Namun, saya tidak dapat melihat referensi ini dalam referensi struktur Array ( https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_Array_Structure/index.html#//apple_ref/swift / struct / s: Sa ) ... jadi saya masih sedikit tidak yakin.

Daniel Howard
sumber
1
Arrayadalah tipe generik: Array<Element>(lihat swiftdoc.org/v2.1/type/Array ), Elementadalah pengganti untuk tipe yang terkandung. Misalnya: var myArray = [Foo]()berarti yang myArrayhanya akan berisi tipe Foo. Foodalam hal ini "dipetakan" ke placeholder generik Element. Jika Anda ingin mengubah perilaku umum Array (melalui ekstensi), Anda akan menggunakan placeholder generik Elementdan bukan jenis beton apa pun (seperti Foo).
David James
5

Menggunakan Swift 2.2 : Saya mengalami masalah serupa ketika mencoba menghapus duplikat dari berbagai string. Saya bisa menambahkan ekstensi pada kelas Array yang tidak hanya apa yang saya cari.

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

Menambahkan dua metode ini ke kelas Array memungkinkan saya untuk memanggil salah satu dari dua metode pada array dan berhasil menghapus duplikat. Perhatikan bahwa elemen dalam array harus sesuai dengan protokol Hashable. Sekarang saya bisa melakukan ini:

 var dupes = ["one", "two", "two", "three"]
 let deDuped = dupes.removeDuplicates()
 dupes.removeDuplicatesInPlace()
 // result: ["one", "two", "three"]
James
sumber
Ini juga bisa diselesaikan let deDuped = Set(dupes), yang bisa Anda kembalikan dengan metode non-destruktif yang disebut toSetasalkan Anda baik-baik saja dengan perubahan jenis
alexpyoung
@alexpyoung Anda akan mengacaukan urutan array jika Anda melakukan Set ()
Danny Wang
5

Jika Anda ingin mempelajari tentang memperluas Array dan jenis kode checkout kelas build in lainnya di repo github ini https://github.com/ankurp/Cent

Pada Xcode 6.1 sintaks untuk memperluas array adalah sebagai berikut

extension Array {
    func at(indexes: Int...) -> [Element] {
        ... // You code goes herer
    }
}
Encore PTL
sumber
1
@Rob Memperbarui URL
Encore PTL
3

Saya telah melihat header perpustakaan standar Swift 2, dan di sini adalah prototipe untuk fungsi filter, yang membuatnya sangat jelas bagaimana cara menggulung sendiri.

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

Ini bukan ekstensi untuk Array, tetapi untuk CollectionType, jadi metode yang sama berlaku untuk jenis koleksi lainnya. @noescape berarti bahwa blok yang dilewati tidak akan meninggalkan ruang lingkup fungsi filter, yang memungkinkan beberapa optimisasi. Diri dengan modal S adalah kelas yang kita perluas. Self.Generator adalah iterator yang beriterasi melalui objek dalam koleksi dan Self.Generator.Element adalah jenis objek, misalnya untuk array [Int?] Self.Generator.Element akan menjadi Int ?.

Semua dalam semua metode filter ini dapat diterapkan ke sembarang CollectionType, perlu blok filter yang mengambil elemen koleksi dan mengembalikan Bool, dan mengembalikan array dari tipe aslinya. Jadi, menyatukan ini, inilah metode yang menurut saya berguna: Menggabungkan peta dan filter, dengan mengambil blok yang memetakan elemen koleksi ke nilai opsional, dan mengembalikan array nilai-nilai opsional yang tidak nol.

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
    }
}
gnasher729
sumber
2
import Foundation

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

( Swift 2.x )

Anda juga dapat memperluas array agar sesuai dengan protokol yang mengandung blue-rpints untuk metode tipe generik, misalnya, protokol yang berisi utilitas fungsional kustom Anda untuk semua elemen array generik yang sesuai dengan beberapa batasan tipe, misalnya protokol MyTypes. Bonus menggunakan pendekatan ini adalah bahwa Anda dapat menulis fungsi yang mengambil argumen array umum, dengan batasan bahwa argumen array ini harus sesuai dengan protokol utilitas fungsi kustom Anda, misalnya protokolMyFunctionalUtils .

Anda bisa mendapatkan perilaku ini baik secara implisit, dengan mengetik membatasi elemen array MyTypes, atau --- seperti yang akan saya tunjukkan dalam metode yang saya jelaskan di bawah ---, cukup rapi, secara eksplisit, membiarkan tajuk fungsi array generik Anda secara langsung menunjukkan bahwa input array sesuai dengan MyFunctionalUtils.


Kita mulai dengan Protokol MyTypesuntuk digunakan sebagai batasan tipe; memperpanjang jenis yang ingin Anda muat dalam obat generik Anda dengan protokol ini (contoh di bawah ini memperluas jenis dasar Intdan Doublejuga jenis khusus 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)
}

Protokol MyFunctionalUtils(memegang cetak biru utilitas fungsi array generik tambahan kami) dan setelah itu, perpanjangan Array oleh MyFunctionalUtils; implementasi metode cetak biru:

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

Akhirnya, tes dan dua contoh menunjukkan fungsi mengambil array generik, dengan kasus-kasus berikut, masing-masing

  1. Menampilkan pernyataan implisit bahwa parameter array sesuai dengan protokol 'MyFunctionalUtils', melalui tipe yang membatasi elemen array ke 'MyTypes' (fungsi bar1).

  2. Menunjukkan secara eksplisit bahwa parameter array sesuai dengan protokol 'MyFunctionalUtils' (fungsi bar2).

Tes dan contoh berikut:

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

    }

}
Durul Dalkanat
sumber
2
Downcast ini ( $0 as! Double) berjuang melawan sistem tipe Swift dan juga mengalahkan tujuan pertanyaan OP, menurut saya. Dengan melakukan itu Anda kehilangan potensi untuk optimisasi kompiler untuk perhitungan yang sebenarnya ingin Anda lakukan, dan Anda juga mencemari namespace Array dengan fungsi yang tidak berarti (mengapa Anda ingin melihat .calculateMedian () dalam array UIViews, atau apa pun kecuali Double dalam hal ini?). Ada cara yang lebih baik.
ephemer
cobaextension CollectionType where Generator.Element == Double {}
ephemer