Como estender matrizes digitadas no Swift?


203

Como posso estender o Swift Array<T>ou T[]digitar com utilitários funcionais personalizados?

Navegar nos documentos da API do Swift mostra que os métodos Array são uma extensão do T[], por exemplo:

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

    var count: Int { get }

    var capacity: Int { get }

    var isEmpty: Bool { get }

    func copy() -> T[]
}

Ao copiar e colar a mesma fonte e tentar variações como:

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

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

Falha ao construir com o erro:

O tipo nominal T[]não pode ser estendido

O uso da definição de tipo completo falha com Use of undefined type 'T', ou seja:

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

E também falha com Array<T : Any>e Array<String>.

Curiosamente Swift me permite estender uma matriz não tipada com:

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

Com o qual eu posso ligar:

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

Mas não consigo criar uma extensão de tipo genérica adequada, pois o tipo parece estar perdido quando flui pelo método, por exemplo, tentando substituir o filtro interno do Swift por :

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

Mas o compilador o trata como não digitado, onde ainda permite chamar o ramal com:

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

E quando percorrido com um depurador indica que o tipo é, Swift.Stringmas é um erro de compilação tentar acessá-lo como uma String sem convertê-lo para o Stringprimeiro, ou seja:

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

Alguém sabe qual é a maneira correta de criar um método de extensão digitado que funciona como as extensões internas?


Votado porque também não consigo encontrar uma resposta. Vendo o mesmo extension T[]bit ao clicar em Command no tipo Array no XCode, mas não vendo nenhuma maneira de implementá-lo sem obter um erro.
nome de usuário tbd

@usernametbd FYI acabou de encontrar, parece que a solução foi remover <T>da assinatura do método.
Mito #

Respostas:


296

Para estender matrizes digitadas com classes , o abaixo funciona para mim (Swift 2.2 ). Por exemplo, classificando uma matriz digitada:

class HighScoreEntry {
    let score:Int
}

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

Tentar fazer isso com uma struct ou typealias dará um erro:

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

Atualização :

Para estender matrizes digitadas com não classes, use a seguinte abordagem:

typealias HighScoreEntry = (Int)

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

No Swift 3, alguns tipos foram renomeados:

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

1
relatórios do compilador que 'SequenceType' foi renomeado para 'Seqüência'
Sandover

Por que você não usou Iterator.Element no tipo de retorno [Iterator.Element]?
gaussblurinc

1
oi, você pode explicar o recurso de conformidade condicional no 4.1? O que há de novo no 4.1? Poderíamos fazer isso no 2.2? O que estou ausente
OSRL

Desde o Swift 3.1, você pode estender matrizes com não classes com a seguinte sintaxe: extension Array where Element == Int
Giles

63

Depois de um tempo tentando coisas diferentes, a solução parece remover o <T>da assinatura 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 agora funciona conforme o esperado, sem erros de compilação:

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

1
BTW O que você definiu aqui é funcionalmente equivalente à filterfunção existente :let x = ["A","B","C","X”].filter { $0.compare("A") > 0 }
Palimondo

5
@Palimondo Não, não é, o filtro interno executa retornos de chamada duas vezes .
mythz

4
Entendo. A filtragem dupla me parece um pouco problemática ... Mas ainda é válido que o valor filteré funcionalmente equivalente ao seu find, ou seja, o resultado da função é o mesmo. Se o fechamento do filtro tiver efeitos colaterais, você poderá não gostar dos resultados, com certeza.
Palimondo

2
@Palimondo Exatamente, o filtro padrão possui um comportamento inesperado, enquanto o impl achado acima funciona como esperado (e por que ele existe). Não é funcionalmente equivalente se ele executa o fechamento duas vezes, o que pode potencialmente alterar variáveis ​​de escopo (que foi o bug que eu encontrei, daí a pergunta sobre seu comportamento). Observe também que a pergunta menciona especificamente o desejo de substituir o built-in do Swift filter.
mythz

4
Parece que estamos discutindo sobre a definição da palavra funcional . Geralmente, no paradigma de programação funcional, de onde as funções filter, mape se reduceoriginam, são executadas para seus valores de retorno. Por outro lado, a eachfunção que você define acima é um exemplo de função executada por seu efeito colateral, porque não retorna nada. Acho que podemos concordar que a implementação atual do Swift não é ideal e a documentação não afirma nada sobre suas características de tempo de execução.
Palimondo 16/06

24

Estenda todos os tipos:

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

Estenda alguns tipos:

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

Estenda um tipo específico :

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

8

Eu tive um problema semelhante - queria estender a matriz geral com um método swap (), que deveria receber um argumento do mesmo tipo que a matriz. Mas como você especifica o tipo genérico? Descobri por tentativa e erro que o abaixo funcionou:

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

A chave para isso era a palavra "elemento". Observe que eu não defini esse tipo em nenhum lugar, ele parece existir automaticamente dentro do contexto da extensão da matriz e se refere a qualquer tipo de elemento da matriz.

Não tenho 100% de certeza do que está acontecendo lá, mas acho que provavelmente é porque 'Element' é um tipo associado da matriz (consulte 'Tipos associados' aqui https://developer.apple.com/library/ios/documentation /Swift/Conceptual/Swift_Programming_Language/Generics.html#//apple_ref/doc/uid/TP40014097-CH26-ID189 )

No entanto, não consigo ver nenhuma referência disso na referência da estrutura Array ( https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_Array_Structure/index.html#//apple_ref/swift / struct / s: Sa ) ... então ainda estou um pouco inseguro.


1
Arrayé um tipo genérico: Array<Element>(consulte swiftdoc.org/v2.1/type/Array ), Elementé um espaço reservado para o tipo contido. Por exemplo: var myArray = [Foo]()significa que myArrayconterá apenas o tipo Foo. Foonesse caso, é "mapeado" para o espaço reservado genérico Element. Se você deseja alterar o comportamento geral da matriz (via extensão), use o espaço reservado genérico Elemente não qualquer tipo concreto (como Foo).
David James

5

Usando o Swift 2.2 : Eu tive um problema semelhante ao tentar remover duplicatas de uma matriz de seqüências de caracteres. Consegui adicionar uma extensão na classe Array que faz exatamente o que eu estava procurando fazer.

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

A adição desses dois métodos à classe Array permite chamar um dos dois métodos em uma matriz e remover duplicatas com êxito. Observe que os elementos na matriz devem estar em conformidade com o protocolo Hashable. Agora eu posso fazer isso:

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

Isso também pode ser feito com let deDuped = Set(dupes), o que você poderia voltar em um método não destrutivo chamado toSetenquanto você está ok com a mudança tipo
alexpyoung

@alexpyoung você estragaria a ordem da matriz se você definir Set ()
Danny Wang

5

Se você quiser aprender sobre como estender matrizes e outros tipos de código de checkout de classes em classes neste repositório do github https://github.com/ankurp/Cent

A partir do Xcode 6.1, a sintaxe para estender matrizes é a seguinte

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

1
@Rob Atualizou a URL
Encore PTL

3

Dei uma olhada nos cabeçalhos da biblioteca padrão do Swift 2 e aqui está o protótipo da função de filtro, o que torna bastante óbvio como usar o seu.

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

Não é uma extensão para Array, mas para CollectionType, portanto, o mesmo método se aplica a outros tipos de coleção. @noescape significa que o bloco passado não deixará o escopo da função de filtro, o que permite algumas otimizações. O eu com S maiúsculo é a classe que estamos estendendo. Self.Generator é um iterador que itera através dos objetos na coleção e Self.Generator.Element é o tipo dos objetos, por exemplo, para uma matriz [Int?] Self.Generator.Element seria Int ?.

No geral, esse método de filtro pode ser aplicado a qualquer CollectionType, ele precisa de um bloco de filtro que pegue um elemento da coleção e retorne um Bool e retorne uma matriz do tipo original. Então, juntando isso, aqui está um método que eu acho útil: ele combina mapa e filtro, pegando um bloco que mapeia um elemento de coleção para um valor opcional e retorna uma matriz desses valores opcionais que não são 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
    }
}

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 )

Você também pode estender a matriz para estar em conformidade com um protocolo contendo blue-rpints para métodos de tipo genérico, por exemplo, um protocolo contendo seus utilitários funcionais personalizados para todos os elementos genéricos da matriz em conformidade com alguma restrição de tipo, digamos protocolo MyTypes. O bônus usando essa abordagem é que você pode escrever funções usando argumentos genéricos de matriz, com uma restrição de que esses argumentos de matriz devem estar em conformidade com o protocolo de utilitários de funções personalizadas, por exemplo, protocolo MyFunctionalUtils.

Você pode obter esse comportamento implicitamente, digitando os elementos da matriz MyTypescomo ---, como mostrarei no método descrito abaixo ---, de maneira bem explícita, deixando seu cabeçalho genérico de funções da matriz mostrar diretamente as matrizes de entrada está em conformidade com MyFunctionalUtils.


Começamos com Protocolos MyTypespara uso como restrição de tipo; estenda os tipos que você deseja ajustar em seus genéricos por este protocolo (o exemplo abaixo estende os tipos fundamentais Inte Doubletambém um 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(mantendo blueprints de nossos utilitários genéricos adicionais de funções de matriz) e, posteriormente, a extensão de Matriz por MyFunctionalUtils; implementação de métodos impressos em 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
    }
}

Por fim, testes e dois exemplos mostrando uma função que recebe matrizes genéricas, com os seguintes casos, respectivamente

  1. Mostrando afirmação implícita de que os parâmetros da matriz estão de acordo com o protocolo 'MyFunctionalUtils', via tipo restringindo os elementos das matrizes a 'MyTypes' (função bar1).

  2. Mostrando explicitamente que os parâmetros da matriz estão em conformidade com o protocolo 'MyFunctionalUtils' (função bar2).

O teste e os exemplos a seguir:

/* 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
Esses downcasts ( $0 as! Double) estão lutando contra o sistema de tipos de Swift e também derrotam o objetivo da pergunta do OP, na minha opinião. Ao fazer isso, você perde todo o potencial de otimizações do compilador para os cálculos que realmente deseja fazer e também polui o espaço de nomes do Array com funções sem sentido (por que você deseja ver .calculateMedian () em uma matriz de UIViews ou de qualquer coisa, exceto o dobro para esse assunto?). Há um caminho melhor.
Ephemer 27/09/15

tenteextension CollectionType where Generator.Element == Double {}
efémero 27/09/2015
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.