¿Cómo puedo extender matrices escritas en Swift?

203

¿Cómo puedo extender Swift Array<T>o T[]escribir con utilidades funcionales personalizadas?

Examinar los documentos API de Swift muestra que los métodos de matriz son una extensión de T[], por ejemplo:

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

    var count: Int { get }

    var capacity: Int { get }

    var isEmpty: Bool { get }

    func copy() -> T[]
}

Al copiar y pegar la misma fuente y probar cualquier variación como:

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

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

No se genera con el error:

El tipo nominal T[]no se puede extender

El uso de la definición de tipo completo falla Use of undefined type 'T', es decir:

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

Y también falla con Array<T : Any>y Array<String>.

Curiosamente, Swift me permite extender una matriz sin tipo con:

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

Con lo cual me deja llamar:

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

Pero no puedo crear una extensión de tipo genérico adecuada, ya que el tipo parece perderse cuando fluye a través del método, por ejemplo, tratando de reemplazar el filtro incorporado de Swift con :

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

Pero el compilador lo trata como sin tipo donde todavía permite llamar a la extensión con:

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

Y cuando se pasa con un depurador indica que el tipo es Swift.Stringpero es un error de compilación intentar acceder a él como una Cadena sin lanzarlo Stringprimero, es decir:

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

¿Alguien sabe cuál es la forma correcta de crear un método de extensión mecanografiado que actúa como las extensiones incorporadas?

mythz
fuente
Elegido porque tampoco puedo encontrar una respuesta. Ver el mismo extension T[]bit cuando Comando-clic en el tipo Array en XCode, pero no ve ninguna forma de implementarlo sin obtener un error.
nombre de usuario tbd
@usernametbd FYI lo encontré, parece que la solución fue eliminar <T>de la firma del método.
mythz

Respuestas:

296

Para extender matrices escritas con clases , lo siguiente funciona para mí (Swift 2.2 ). Por ejemplo, ordenando una matriz escrita:

class HighScoreEntry {
    let score:Int
}

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

Intentar hacer esto con una estructura o typealias dará un error:

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

Actualizar :

Para ampliar las matrices escritas con no clases, utilice el siguiente enfoque:

typealias HighScoreEntry = (Int)

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

En Swift 3 algunos tipos han sido renombrados:

extension Sequence where Iterator.Element == HighScoreEntry 
{
    // ...
}
Andrew Schreiber
fuente
1
informes del compilador que 'SequenceType' se ha cambiado el nombre a 'Secuencia'
Sandover
¿Por qué no usaste Iterator.Element en el tipo de retorno [Iterator.Element]?
gaussblurinc
1
hola, ¿puedes explicar la función de conformidad condicional en 4.1? ¿Qué hay de nuevo en 4.1? ¿Podríamos hacer eso en 2.2? ¿Qué me estoy perdiendo
Osrl
Desde Swift 3.1 puede ampliar matrices con no clases con la siguiente sintaxis: extensión Array donde Element == Int
Giles
63

Después de un tiempo probando cosas diferentes, la solución parece eliminar el <T>de la firma como:

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

Que ahora funciona según lo previsto sin errores de compilación:

["A","B","C"].find { $0.compare("A") > 0 }
mythz
fuente
1
Por cierto, lo que ha definido aquí es funcionalmente equivalente a la filterfunción existente :let x = ["A","B","C","X”].filter { $0.compare("A") > 0 }
Palimondo
44
Veo. El filtrado doble me parece bastante defectuoso ... Pero todavía sostiene que filteres funcionalmente equivalente a su find, es decir, el resultado de la función es el mismo. Si el cierre de su filtro tiene efectos secundarios, seguramente no le gusten los resultados.
Palimondo
2
@Palimondo Exactamente, el filtro predeterminado tiene un comportamiento inesperado, mientras que lo anterior find impl funciona como se esperaba (y por qué existe). No es funcionalmente equivalente si ejecuta el cierre dos veces, lo que puede mutar potencialmente las variables de ámbito (que resultó ser un error que encontré, de ahí la pregunta sobre su comportamiento). También tenga en cuenta que la pregunta menciona específicamente querer reemplazar el Swift incorporado filter.
mythz
44
Parece que estamos discutiendo sobre la definición de la palabra funcional . Habitualmente, en el paradigma de programación funcional en los filter, mapy reducelas funciones se originan de, las funciones se ejecutan por sus valores de retorno. Por el contrario, la eachfunción que define arriba es un ejemplo de una función ejecutada por su efecto secundario, porque no devuelve nada. Creo que podemos estar de acuerdo en que la implementación actual de Swift no es ideal y la documentación no indica nada sobre sus características de tiempo de ejecución.
Palimondo
24

Extienda todos los tipos:

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

Extiende algunos tipos:

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

Extiende un tipo particular :

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

Tuve un problema similar: quería extender la matriz general con un método swap (), que se suponía que debía tomar un argumento del mismo tipo que la matriz. Pero, ¿cómo se especifica el tipo genérico? Descubrí por prueba y error que lo siguiente funcionó:

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

La clave fue la palabra 'Elemento'. Tenga en cuenta que no definí este tipo en ninguna parte, parece existir automáticamente dentro del contexto de la extensión de la matriz, y se refiere a cualquier tipo de elementos de la matriz.

No estoy 100% seguro de lo que está sucediendo allí, pero creo que probablemente se deba a que 'Elemento' es un tipo asociado de la matriz (consulte 'Tipos asociados' aquí https://developer.apple.com/library/ios/documentation /Swift/Conceptual/Swift_Programming_Language/Generics.html#//apple_ref/doc/uid/TP40014097-CH26-ID189 )

Sin embargo, no puedo ver ninguna referencia de esto en la referencia de la estructura Array ( https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_Array_Structure/index.html#//apple_ref/swift / struct / s: Sa ) ... así que todavía estoy un poco inseguro.

Daniel Howard
fuente
1
Arrayes un tipo genérico: Array<Element>(ver swiftdoc.org/v2.1/type/Array ), Elementes un marcador de posición para el tipo contenido. Por ejemplo: var myArray = [Foo]()significa que myArraysolo contendrá tipo Foo. Fooen este caso se "asigna" al marcador de posición genérico Element. Si desea cambiar el comportamiento general de Array (a través de la extensión), usaría el marcador de posición genérico Elementy no cualquier tipo concreto (como Foo).
David James
5

Usando Swift 2.2 : me encontré con un problema similar al intentar eliminar duplicados de una serie de cadenas. Pude agregar una extensión en la clase Array que hace exactamente lo que estaba buscando hacer.

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

Agregar estos dos métodos a la clase Array me permite llamar a uno de los dos métodos en una matriz y eliminar con éxito los duplicados. Tenga en cuenta que los elementos en la matriz deben cumplir con el protocolo Hashable. Ahora puedo hacer esto:

 var dupes = ["one", "two", "two", "three"]
 let deDuped = dupes.removeDuplicates()
 dupes.removeDuplicatesInPlace()
 // result: ["one", "two", "three"]
James
fuente
Esto también se podría lograr con let deDuped = Set(dupes)lo que podría devolver en un método no destructivo llamado toSetsiempre y cuando esté de acuerdo con el cambio de tipo
alexpyoung el
@alexpyoung, podrías estropear el orden de la matriz si haces Set ()
Danny Wang
5

Si desea obtener información sobre cómo extender Arrays y otros tipos de código de pago de compilación en clases en este repositorio de Github https://github.com/ankurp/Cent

A partir de Xcode 6.1, la sintaxis para extender las matrices es la siguiente

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

Eché un vistazo a los encabezados de la biblioteca estándar de Swift 2, y aquí está el prototipo de la función de filtro, lo que hace que sea bastante obvio cómo rodar el suyo.

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

No es una extensión de Array, sino de CollectionType, por lo que el mismo método se aplica a otros tipos de colección. @noescape significa que el bloque pasado no dejará el alcance de la función de filtro, lo que permite algunas optimizaciones. El yo con S mayúscula es la clase que estamos ampliando. Self.Generator es un iterador que recorre los objetos de la colección y Self.Generator.Element es el tipo de objetos, por ejemplo, para una matriz [Int?] Self.Generator.Element sería Int ?.

En general, este método de filtro se puede aplicar a cualquier CollectionType, necesita un bloque de filtro que tome un elemento de la colección y devuelva un Bool, y devuelve una matriz del tipo original. Así que, juntando esto, aquí hay un método que me parece útil: combina mapa y filtro, al tomar un bloque que asigna un elemento de colección a un valor opcional, y devuelve una matriz de esos valores opcionales que no son nulos.

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
fuente
2
import Foundation

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

( Swift 2.x )

También puede extender la matriz para que se ajuste a un protocolo que contenga blue-rpints para métodos de tipo genérico, por ejemplo, un protocolo que contenga sus utilidades funcionales personalizadas para todos los elementos de matriz genéricos que se ajusten a alguna restricción de tipo, por ejemplo, protocolo MyTypes. La ventaja de usar este enfoque es que puede escribir funciones tomando argumentos de matriz genéricos, con la restricción de que estos argumentos de matriz deben ajustarse a su protocolo de utilidades de funciones personalizadas, digamos protocolo MyFunctionalUtils.

Puede obtener este comportamiento implícitamente, al restringir los elementos de la matriz a MyTypes, o --- como mostraré en el método que describo a continuación ---, de manera bastante clara y explícita, dejando que el encabezado genérico de las funciones de la matriz muestre directamente las matrices de entrada se ajusta a MyFunctionalUtils.


Comenzamos con Protocolos MyTypespara usar como restricción de tipo; amplíe los tipos que desea incluir en sus genéricos mediante este protocolo (el ejemplo a continuación amplía los tipos fundamentales Inty Doubletambién un tipo personalizado 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)
}

Protocolo MyFunctionalUtils(que contiene planos de nuestras utilidades de funciones de matriz genéricas adicionales) y, posteriormente, la extensión de Array por MyFunctionalUtils; implementación de métodos impresos en azul:

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

Finalmente, pruebas y dos ejemplos que muestran una función que toma matrices genéricas, con los siguientes casos, respectivamente

  1. Mostrando una afirmación implícita de que los parámetros de la matriz se ajustan al protocolo 'MyFunctionalUtils', a través del tipo que restringe los elementos de la matriz a 'MyTypes' (función bar1).

  2. Mostrando explícitamente que los parámetros de la matriz se ajustan al protocolo 'MyFunctionalUtils' (función bar2).

La prueba y los ejemplos siguientes:

/* 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
fuente
-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
fuente
2
Estos abatidos ( $0 as! Double) están luchando contra el sistema de tipos de Swift y, en mi opinión, también anulan el propósito de la pregunta del OP. Al hacerlo, está perdiendo cualquier potencial para las optimizaciones del compilador para los cálculos que realmente desea hacer, y también está contaminando el espacio de nombres de Array con funciones sin sentido (¿por qué querría ver .calculateMedian () en una matriz de UIViews, o de cualquier cosa menos doble para el caso?). Hay una mejor manera.
Ephemer
tryextension CollectionType where Generator.Element == Double {}
ephemer