Jak mogę rozszerzyć tablice pisane w Swift?


203

Jak mogę rozszerzyć Swift Array<T>lub T[]pisać za pomocą niestandardowych narzędzi funkcjonalnych?

Przeglądanie dokumentów API Swift pokazuje, że metody Array są rozszerzeniem T[]np .:

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

    var count: Int { get }

    var capacity: Int { get }

    var isEmpty: Bool { get }

    func copy() -> T[]
}

Podczas kopiowania i wklejania tego samego źródła i wypróbowywania wszelkich odmian, takich jak:

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

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

Nie można zbudować z błędem:

Typ nominalny T[]nie może zostać przedłużony

Używanie pełnej definicji typu kończy się niepowodzeniem Use of undefined type 'T', tzn .:

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

I to również nie działa z Array<T : Any>i Array<String>.

Co ciekawe, Swift pozwala mi rozszerzyć nietypową tablicę o:

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

Który pozwala mi dzwonić z:

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

Ale nie mogę utworzyć właściwego rozszerzenia typu ogólnego, ponieważ typ wydaje się być utracony, gdy przepływa przez metodę, np. Próbuje zastąpić wbudowany filtr Swift :

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

Ale kompilator traktuje go jako bez typu, w którym nadal pozwala wywoływać rozszerzenie za pomocą:

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

A kiedy krok po kroku z debuggerem wskazuje, że typ jest, Swift.Stringale błędem kompilacji jest próba uzyskania dostępu jak ciąg bez rzutowania go na Stringpierwsze, tj .:

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

Czy ktoś wie, jaki jest właściwy sposób na utworzenie typowej metody rozszerzenia, która działa jak wbudowane rozszerzenia?


Głosowałem, ponieważ ja też nie mogę znaleźć odpowiedzi. Widząc ten sam extension T[]bit po kliknięciu Command typu Array w XCode, ale nie widząc żadnego sposobu na jego wdrożenie bez otrzymania błędu.
nazwa użytkownika tbd

@usernametbd FYI właśnie go znalazłem, wygląda na to, że rozwiązaniem było usunięcie <T>z podpisu metody.
mythz

Odpowiedzi:


296

Aby rozszerzyć tablice typowe o klasy , poniżej działa dla mnie (Swift 2.2 ). Na przykład, sortowanie tablicy o typie:

class HighScoreEntry {
    let score:Int
}

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

Próba zrobienia tego za pomocą struct lub typali spowoduje błąd:

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

Aktualizacja :

Aby rozszerzyć tablice typowane o nieklasy, należy zastosować następujące podejście:

typealias HighScoreEntry = (Int)

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

W Swift 3 zmieniono nazwy niektórych typów:

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

1
kompilator informuje, że zmieniono nazwę „SequenceType” na „Sequence”
sandover,

Dlaczego nie użyłeś Iterator.Element jako typu zwrotu [Iterator.Element]?
gaussblurinc

1
cześć, czy możesz wyjaśnić funkcję warunkowej zgodności w 4.1? Co nowego w 4.1? Możemy to zrobić w wersji 2.2? Czego mi brakuje
osrl

Od wersji Swift 3.1 możesz rozszerzać tablice o nieklasy z następującą składnią: rozszerzenie Tablica gdzie Element == Int
Giles

63

Po chwili wypróbowania różnych rzeczy rozwiązanie wydaje się usuwać <T>z podpisu, takie jak:

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

Który teraz działa zgodnie z przeznaczeniem bez błędów kompilacji:

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

1
BTW To, co tu zdefiniowałeś, jest funkcjonalnie równoważne z istniejącą filterfunkcją:let x = ["A","B","C","X”].filter { $0.compare("A") > 0 }
Palimondo


4
Widzę. Podwójne filtrowanie wydaje mi się raczej błędne ... Ale nadal utrzymuje, że filterjest ono funkcjonalnie równoważne z twoim find, tzn. Wynik funkcji jest taki sam. Jeśli zamknięcie filtra ma skutki uboczne, z pewnością wyniki mogą Ci się nie podobać.
Palimondo

2
@Palimondo Dokładnie, domyślny filtr zachowuje się nieoczekiwanie, podczas gdy powyższy find impl działa zgodnie z oczekiwaniami (i dlaczego istnieje). Nie jest funkcjonalnie równoważny, jeśli wykonuje zamknięcie dwa razy, co może potencjalnie mutować zmienne o zasięgu (tak się złożyło, że wpadłem na błąd, stąd pytanie o jego zachowanie). Zwróć też uwagę na pytanie, które konkretnie wspomina o zamianie wbudowanego w Swift filter.
mit

4
Wydaje się, że spieramy się o definicję słowa funkcjonalny . Zwyczajowo, w paradygmacie programowania funkcjonalnego, gdzie filter, mapi reducefunkcje pochodzą z funkcje są realizowane na ich wartości zwracanych. Dla kontrastu, eachfunkcja zdefiniowana powyżej jest przykładem funkcji wykonanej dla jej efektu ubocznego, ponieważ nic nie zwraca. Wydaje mi się, że możemy się zgodzić, że obecna implementacja Swift nie jest idealna, a dokumentacja nie zawiera żadnych informacji na temat jej właściwości.
Palimondo

24

Rozszerz wszystkie typy:

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

Rozszerz niektóre typy:

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

Rozszerz określony typ:

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

8

Miałem podobny problem - chciałem rozszerzyć ogólny Array za pomocą metody swap (), która miała przyjmować argument tego samego typu co tablica. Ale jak określić typ ogólny? Odkryłem metodą prób i błędów, że poniższe działania:

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

Kluczem do tego było słowo „Element”. Zauważ, że nigdzie nie zdefiniowałem tego typu, wydaje się, że automatycznie istnieje w kontekście rozszerzenia tablicy i odnoszę się do dowolnego typu elementów tablicy.

Nie jestem w 100% pewien, co się tam dzieje, ale myślę, że prawdopodobnie dlatego, że „Element” jest powiązanym typem tablicy (patrz „Powiązane typy” tutaj https://developer.apple.com/library/ios/documentation /Swift/Conceptual/Swift_Programming_Language/Generics.html#//apple_ref/doc/uid/TP40014097-CH26-ID189 )

Jednak nie widzę żadnego odniesienia do tego w odwołaniu do struktury tablicy ( https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_Array_Structure/index.html#//apple_ref/swift / struct / s: Sa ) ... więc wciąż jestem trochę niepewny.


1
Arrayjest typem ogólnym: Array<Element>(patrz swiftdoc.org/v2.1/type/Array ), Elementjest symbolem zastępczym dla zawartego typu. Na przykład: var myArray = [Foo]()oznacza, że myArraybędzie zawierał tylko typ Foo. Foow tym przypadku jest „mapowany” na ogólny symbol zastępczy Element. Jeśli chcesz zmienić ogólne zachowanie Array (poprzez rozszerzenie), użyjesz ogólnego symbolu zastępczego, Elementa nie żadnego konkretnego typu (takiego jak Foo).
David James

5

Używając Swift 2.2 : napotkałem podobny problem podczas próby usunięcia duplikatów z tablicy ciągów. Byłem w stanie dodać rozszerzenie do klasy Array, które robi dokładnie to, czego chciałem.

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

Dodanie tych dwóch metod do klasy Array pozwala mi wywołać jedną z dwóch metod w tablicy i pomyślnie usunąć duplikaty. Zauważ, że elementy w tablicy muszą być zgodne z protokołem Hashable. Teraz mogę to zrobić:

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

Można to również osiągnąć za pomocą let deDuped = Set(dupes)metody nieniszczącej o nazwie toSettak długo, jak długo możesz zmienić typ
alepypyoung

@alexpyoung zrobiłbyś bałagan w porządku tablicy, jeśli wykonasz Set ()
Danny Wang

5

Jeśli chcesz dowiedzieć się o rozszerzaniu tablic i innych typach klas wbudowanych kod kasy w tym repozytorium github https://github.com/ankurp/Cent

Począwszy od Xcode 6.1, składnia rozszerzania tablic jest następująca

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

1
@Rob Zaktualizowano adres URL
Encore PTL,

3

Rzuciłem okiem na standardowe nagłówki bibliotek Swift 2, a oto prototyp funkcji filtrującej, co sprawia, że ​​dość oczywiste jest, jak tworzyć własne.

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

Nie jest rozszerzeniem Array, ale CollectionType, więc ta sama metoda ma zastosowanie do innych typów kolekcji. @noescape oznacza, że ​​przekazany blok nie opuści zakresu funkcji filtrowania, co umożliwia pewne optymalizacje. Jaźń z dużą literą S to klasa, którą poszerzamy. Self.Generator to iterator, który iteruje po obiektach w kolekcji, a Self.Generator.Element jest typem obiektów, na przykład dla tablicy [Int?] Self.Generator.Element będzie Int ?.

Podsumowując, ta metoda filtrowania może być zastosowana do dowolnego typu CollectionType, potrzebuje bloku filtra, który pobiera element kolekcji i zwraca wartość Bool i zwraca tablicę typu oryginalnego. Podsumowując, oto metoda, która uważam za przydatną: Łączy mapę i filtr, biorąc blok, który odwzorowuje element kolekcji na opcjonalną wartość, i zwraca tablicę tych opcjonalnych wartości, które nie są równe zero.

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 )

Można także rozszerzyć tablicę, aby była zgodna z protokołem zawierającym niebieskie znaki dla metod typu ogólnego, np. Protokół zawierający niestandardowe narzędzia funkcjonalne dla wszystkich elementów tablicy ogólnej zgodnych z pewnym ograniczeniem typu, powiedzmy protokół MyTypes. Zaletą tego podejścia jest to, że możesz pisać funkcje, biorąc ogólne argumenty tablicowe, z ograniczeniem, że te argumenty tablicowe muszą być zgodne z protokołem narzędzi funkcji niestandardowych, powiedzmy protokółMyFunctionalUtils .

Możesz uzyskać to zachowanie albo pośrednio, wpisując typ ograniczający elementy tablicy MyTypes, albo --- jak pokażę w metodzie, którą opisuję poniżej ---, całkiem starannie, wyraźnie, pozwalając nagłówkowi ogólnych funkcji tablicy bezpośrednio pokazywać tablice wejściowe jest zgodny z MyFunctionalUtils.


Zaczynamy od protokołów MyTypesdo stosowania jako ograniczenie typu; rozszerzyć typy, które chcesz dopasować do generycznych za pomocą tego protokołu (przykład poniżej rozszerza typy podstawowe, Inta Doubletakże niestandardowy typ 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)
}

Protokół MyFunctionalUtils(przechowujący plany naszych dodatkowych ogólnych funkcji funkcji tablicy), a następnie rozszerzenie Array przez MyFunctionalUtils; wdrożenie metody (metod) drukowanej na niebiesko:

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

Wreszcie testy i dwa przykłady pokazujące funkcję przyjmującą tablice ogólne, odpowiednio w następujących przypadkach

  1. Pokazywanie niejawnego twierdzenia, że ​​parametry tablicy są zgodne z protokołem „MyFunctionalUtils”, poprzez typ ograniczający elementy tablic do „MyTypes” (funkcja bar1).

  2. Pokazano wyraźnie , że parametry macierzy zgodne z protokołem „MyFunctionalUtils” (funkcja bar2).

Test i przykłady są następujące:

/* 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
Ci spuszczeni ($0 as! Double ) walczą z systemem typu Swift, a także, moim zdaniem, pokonują cel pytania OP. W ten sposób tracisz potencjał optymalizacji kompilatora dla obliczeń, które faktycznie chcesz wykonać, a także zanieczyszczasz przestrzeń nazw Array bezsensownymi funkcjami (dlaczego chcesz widzieć .calculateMedian () w tablicy UIViews, lub w tym przypadku oprócz Double?). Jest lepszy sposób.
efemeryczny

tryextension CollectionType where Generator.Element == Double {}
efemeryczny
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.