Swift'te yazılan Dizileri nasıl genişletebilirim?


203

Swift'i Array<T>veya T[]türünü özel işlevsel araçlarla nasıl genişletebilirim ?

Swift'in API belgelerine göz atmak, Array yöntemlerinin aşağıdakilerin bir uzantısı olduğunu gösterir T[]:

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

    var count: Int { get }

    var capacity: Int { get }

    var isEmpty: Bool { get }

    func copy() -> T[]
}

Aynı kaynağı kopyalayıp yapıştırırken ve aşağıdakiler gibi varyasyonları denerken:

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

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

Şu hatayla oluşturulamaz:

Nominal tip T[]genişletilemez

Tam tür tanımını kullanmak başarısız olur Use of undefined type 'T', yani:

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

Ve Array<T : Any>ve ile başarısız olur Array<String>.

Merakla Swift, türlenmemiş bir diziyi aşağıdakilerle genişletmeme izin verir:

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

Hangi ile aramamı sağlar:

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

Ancak, tür yöntem aracılığıyla aktığında türü kaybolmuş gibi göründüğü gibi uygun bir genel tür uzantısı oluşturamıyorum, örneğin Swift yerleşik filtre yerine :

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

Ancak derleyici, uzantıyı yine de çağırmaya izin verdiği yerlerde türsüz olarak kabul eder:

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

Ve bir hata ayıklayıcı ile step-thru tür olduğunu gösterir, Swift.Stringancak Stringilk önce döküm olmadan bir String gibi erişmeyi denemek için bir inşa hatası , yani:

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

Yerleşik uzantılar gibi davranan yazılı bir uzantı yöntemi oluşturmanın doğru yolunun ne olduğunu bilen var mı?


Oy verdim çünkü kendime de bir cevap bulamıyorum. XCode'da extension T[]Array türüne Komut tıklatıldığında aynı biti görüyor, ancak hata almadan uygulamak için herhangi bir yol görmüyoruz.
kullanıcı adı tbd

@usernametbd FYI bunu buldu, çözüm <T>yöntem imzasından kaldırmak gibi görünüyor .
mythz

Yanıtlar:


297

Yazılan dizileri sınıflarla genişletmek için aşağıdakiler işe yarar (Swift 2.2 ). Örneğin, yazılan bir diziyi sıralama:

class HighScoreEntry {
    let score:Int
}

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

Bunu bir yapı veya typealias ile yapmaya çalışmak bir hata verecektir:

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

Güncelleme :

Sınıflandırılmamış dizileri sınıf olmayanlarla genişletmek için aşağıdaki yaklaşımı kullanın:

typealias HighScoreEntry = (Int)

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

In Swift 3 bazı türleri yeniden adlandırıldı:

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

1
derleyici, '
DiziTürü'nün

Neden dönüş türünde Iterator.Element kullanmadınız [Iterator.Element]?
gaussblurinc

1
merhaba, 4.1'deki Koşullu Uyum özelliğini açıklayabilir misiniz? 4.1'deki yenilikler 2.2 de yapabilir miyiz? Ne eksik
Osrl

Swift 3.1'den bu yana, sınıfları olmayan dizileri şu sözdizimiyle genişletebilirsiniz: Extension Array burada Element == Int
Giles

63

Bir süre sonra farklı şeyler denedikten sonra çözüm <T>imzadan aşağıdaki gibi çıkarılır :

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

Şimdi derleme hataları olmadan amaçlandığı gibi çalışır:

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

1
BTW Burada tanımladığınız şey işlevsel olarak mevcut filterişleve eşdeğerdir :let x = ["A","B","C","X”].filter { $0.compare("A") > 0 }
Palimondo


4
Anlıyorum. Çift filtreleme benim için oldukça hatalı görünüyor ... Ama yine filterde işlevsel olarak seninle eşdeğer olduğunu find, yani işlevin sonucunun aynı olduğunu iddia ediyor . Filtre kapağınızın yan etkileri varsa, sonuçları kesinlikle beğenmeyebilirsiniz.
Palimondo

2
@Palimondo Tam olarak, varsayılan filtrenin beklenmedik bir davranışı vardır, yukarıdaki buluntu impl beklendiği gibi çalışır (ve neden var olduğunu). Kapanmayı iki kez yürütürse işlevsel olarak eşdeğer değildir, bu da kapsam dahilindeki değişkenleri potansiyel olarak mutasyona uğratabilir (bu, karşılaştığım hata, dolayısıyla davranışıyla ilgili soru). Ayrıca soruna özellikle Swift'in yerleşik yerini değiştirmek istediğinden bahsediliyor filter.
mythz

4
İşlevsel sözcüğün tanımı üzerinde tartışıyor gibiyiz . Adet olarak, fonksiyonel programlama paradigması içinde filter, mapve reduceişlevleri kaynaklı, fonksiyonlar onların dönüş değerleri için yürütülür. Buna karşılık, eachyukarıda tanımladığınız işlev, yan etkisi için yürütülen bir işleve örnektir, çünkü hiçbir şey döndürmez. Sanırım mevcut Swift uygulamasının ideal olmadığını ve belgelerin çalışma zamanı özellikleri hakkında hiçbir şey ifade etmediğini kabul edebiliriz.
Palimondo

24

Tüm türleri genişlet :

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

Bazı türleri genişletin :

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

Belirli bir türü genişletme :

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

8

Ben benzer bir sorun vardı - dizi ile aynı tür bir argüman almak gerekiyordu bir genel swap () yöntemi ile Array genişletmek istedim. Ancak genel türü nasıl belirlersiniz? Deneme yanılma yöntemiyle aşağıdakilerin işe yaradığını gördüm:

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

Bunun anahtarı 'Öğe' kelimesiydi. Bu türü hiçbir yerde tanımlamadığımı, dizi uzantısı bağlamında otomatik olarak var olduğunu ve dizinin öğelerinin türünün ne olduğuna bakın.

Orada neler olduğundan% 100 emin değilim, ama muhtemelen 'Element' Array ile ilişkili bir tür olduğu için düşünüyorum (bkz. 'İlişkili Türler' burada https://developer.apple.com/library/ios/documentation /Swift/Conceptual/Swift_Programming_Language/Generics.html#//apple_ref/doc/uid/TP40014097-CH26-ID189 )

Ancak, Array yapı başvurusunda bununla ilgili herhangi bir başvuru göremiyorum ( https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_Array_Structure/index.html#//apple_ref/swift / struct / s: Sa ) ... bu yüzden hala emin değilim.


1
Arraygenel bir türdür: Array<Element>(bkz. swiftdoc.org/v2.1/type/Array ), Elementiçerilen tür için bir yer tutucudur. Örneğin: yalnızca tür içereceği var myArray = [Foo]()anlamına gelir . bu durumda genel yer tutucuyla "eşlenir" . Array'ın genel davranışını değiştirmek istiyorsanız (uzantı aracılığıyla), herhangi bir somut türü (Foo gibi) değil , genel yer tutucuyu kullanırsınız . myArrayFooFooElementElement
David James

5

Swift 2.2 kullanarak : Bir dizelerden yinelenenleri kaldırmaya çalışırken benzer bir sorunla karşılaştım. Array sınıfına sadece yapmak istediğim şeyi yapan bir uzantı ekleyebildim.

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

Bu iki yöntemi Array sınıfına eklemek, bir dizideki iki yöntemden birini çağırmamı ve yinelenenleri başarıyla kaldırmamı sağlar. Dizideki öğelerin Hashable protokolüne uygun olması gerektiğini unutmayın. Şimdi bunu yapabilirim:

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

Bu , tür değişikliği ile tamamlandığınız sürece let deDuped = Set(dupes)yıkıcı olmayan bir yöntemle geri dönebileceğiniz de gerçekleştirilebilirtoSet
alexpyoung

@alexpyoung Set () yaparsanız dizinin sırasını bozarsınız
Danny Wang

5

Bu github deposundaki Dizileri ve diğer derleme türlerini genişletme hakkında bilgi edinmek istiyorsanız https://github.com/ankurp/Cent

Xcode 6.1'den itibaren dizileri genişletmek için sözdizimi aşağıdaki gibidir

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

1
@Rob URL'yi güncelledi
Encore PTL

3

Swift 2 standart kütüphane başlıklarına bir göz attım ve işte filtre işlevinin prototipi, bu da kendinizinkini nasıl yuvarlayacağınızı açıkça gösteriyor.

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

Bu, Array için değil, CollectionType için bir uzantıdır, bu nedenle aynı yöntem diğer toplama türleri için de geçerlidir. @noescape, iletilen bloğun, bazı optimizasyonlara olanak tanıyan filtre işlevinin kapsamından ayrılmayacağı anlamına gelir. S sermayeli benlik, genişlettiğimiz sınıftır. Self.Generator, koleksiyondaki nesneler arasında yineleme yapan bir yineleyicidir ve Self.Generator.Element, örneğin [Int?] Self.Generator.Element dizisi için nesnelerin tipidir.

Tüm bu filtre yöntemi herhangi bir CollectionType'a uygulanabilir, koleksiyonun bir öğesini alan ve bir Bool döndüren bir filtre bloğuna ihtiyaç duyar ve orijinal türden bir dizi döndürür. Bunu bir araya getirerek, yararlı bulduğum bir yöntem var: Bir toplama öğesini isteğe bağlı bir değere eşleyen bir blok alarak ve nil olmayan bu isteğe bağlı değerlerin bir dizisini döndürerek, harita ve filtreyi birleştirir.

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

( Hızlı 2.x )

Ayrıca diziyi, genel yazım yöntemleri için mavi rpint içeren bir protokole uyacak şekilde genişletebilirsiniz; örneğin, bir tür kısıtlamasına uygun tüm genel dizi elemanları için özel fonksiyonel araçlarınızı içeren bir protokol, örneğin protokol MyTypes. Bu yaklaşımı kullanarak bonus, genel dizi bağımsız değişkenleri alan işlevler yazabilmenizdir, bu dizi bağımsız değişkenlerinin özel işlev yardımcı programları protokolünüze uyması gereken bir kısıtla, örneğin protokol MyFunctionalUtils.

Bu davranışı örtülü olarak, dizi öğelerini sınırlandırarak yazarak MyTypesveya --- aşağıda açıkladığım yöntemde göstereceğim gibi ---, oldukça düzgün, açık bir şekilde, genel dizi işlevlerinizin üstbilginin doğrudan giriş dizilerini göstermesine izin vererek uygun MyFunctionalUtils.


MyTypesTip kısıtı olarak kullanılacak Protokollerle başlıyoruz ; bu protokolle jeneriklerinize uymak istediğiniz türleri genişletin (aşağıdaki örnek temel türleri Intve Doubleözel bir türü genişletir 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(ek jenerik dizi fonksiyonları yardımcı programlarımızın planlarını tutarak) ve daha sonra Array by uzantısını MyFunctionalUtils; mavi baskılı yöntem (ler) in uygulanması:

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

Son olarak, aşağıdaki durumlarda sırasıyla, genel dizileri alan bir işlevi gösteren testler ve iki örnek

  1. Gösterilen örtük 'MyTypes' (işlev için diziler elemanları kısıtlayıcı türü yoluyla, dizi parametreleri protokolü 'MyFunctionalUtils' uygun olduğu iddiasını bar1).

  2. Gösterilen açıkça dizi parametreler 'MyFunctionalUtils' (işlev protokolüne uygun olmasını bar2).

Test ve örnekler aşağıdaki gibidir:

/* 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
Bu inişler ( $0 as! Double) Swift'in tip sistemine karşı savaşıyor ve bence OP'nin sorunun amacını da bozuyor. Bunu yaparak, aslında yapmak istediğiniz hesaplamalar için derleyici optimizasyonları için herhangi bir potansiyel kaybedersiniz ve ayrıca Array'ın ad alanını anlamsız işlevlerle (neden bir UIViews dizisinde .calculateMedian () görmek istersiniz) veya bu konuda Double dışında bir şey var mı?). Daha iyi bir yol var.
ephemer

deneyinextension CollectionType where Generator.Element == Double {}
ephemer
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.