Как я могу расширить типизированные массивы в Swift?

Как я могу расширить тип Swift Array<T> или T[] с помощью пользовательских функциональных utils?

Просмотр документов Swift API показывает, что методы Array являются расширением T[], например:

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

    var count: Int { get }

    var capacity: Int { get }

    var isEmpty: Bool { get }

    func copy() -> T[]
}

При копировании и вставке одного и того же источника и выполнении любых изменений, например:

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

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

Не удается построить с ошибкой:

Номинальный тип T[] не может быть расширен

Использование полного определения типа не выполняется с помощью Use of undefined type 'T', i.e:

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

И он также терпит неудачу с Array<T : Any> и Array<String>.

Любопытно. Swift позволяет мне расширять нетипизированный массив с помощью:

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

Что он позволяет мне звонить с помощью:

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

Но я не могу создать правильное расширение общего типа, поскольку тип кажется потерянным, когда он протекает через метод, например, пытается заменить встроенный фильтр 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
    }
}

Но компилятор рассматривает его как нетипизированный, где он по-прежнему позволяет вызывать расширение с помощью:

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

И когда stepper-thru с отладчиком указывает, что тип Swift.String, но он создает ошибку сборки, чтобы попытаться получить доступ к ней, как String, не отбрасывая ее на String сначала, i.e:

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

Кто-нибудь знает, как правильно создать типизированный метод расширения, который действует как встроенные расширения?

Ответ 1

Для расширения типизированных массивов с классами ниже работает для меня (Swift 2.2). Например, сортировка типизированного массива:

class HighScoreEntry {
    let score:Int
}

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

Попытка сделать это с помощью struct или typealias приведет к ошибке:

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

Обновить:

Чтобы расширить типизированные массивы с помощью неклассов, используйте следующий подход:

typealias HighScoreEntry = (Int)

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

В Swift 3 некоторые типы были переименованы:

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

Ответ 2

Через некоторое время, попробовав разные вещи, решение, похоже, удаляет <T> из подписи, например:

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

Что теперь работает, как предполагалось, без ошибок сборки:

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

Ответ 3

У меня была аналогичная проблема - хотелось расширить общий массив с помощью метода swap(), который должен был принимать аргумент того же типа, что и массив. Но как вы определяете общий тип? Я обнаружил в результате проб и ошибок, что ниже работало:

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

Ключом к этому было слово "Элемент". Обратите внимание, что я не определял этот тип в любом месте, он, кажется, автоматически существует в контексте расширения массива и относится к типу элементов массива.

Я не уверен на 100%, что происходит там, но я думаю, что это, вероятно, потому, что "Элемент" является связанным типом массива (см. "Связанные типы" здесь https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Generics.html#//apple_ref/doc/uid/TP40014097-CH26-ID189)

Однако я не вижу ссылки на это в структуре структуры массива (https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_Array_Structure/index.html#//apple_ref/swift/struct/s:Sa)... поэтому я все еще немного не уверен.

Ответ 4

Использование Swift 2.2: Я столкнулся с подобной проблемой при попытке удалить дубликаты из массива строк. Мне удалось добавить расширение класса Array, которое делает именно то, что я хотел сделать.

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

Добавление этих двух методов в класс Array позволяет мне вызвать один из двух методов в массиве и успешно удалить дубликаты. Обратите внимание, что элементы массива должны соответствовать протоколу Hashable. Теперь я могу это сделать:

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

Ответ 5

Если вы хотите узнать о расширении массивов и других типов сборки в классах кода проверки в этом github repo https://github.com/ankurp/Cent

Как и в Xcode 6.1, синтаксис для расширения массивов выглядит следующим образом

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

Ответ 6

Я посмотрел на стандартные заголовки Swift 2, и вот прототип функции фильтра, что делает его совершенно очевидным, как сворачивать свои собственные.

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

Это не расширение для массива, а для CollectionType, поэтому тот же метод применяется к другим типам коллекций. @noescape означает, что переданный блок не покинет область действия функции фильтра, что позволяет некоторые оптимизации. Я с капиталом S - это класс, который мы расширяем. Self.Generator - это итератор, который выполняет итерации через объекты в коллекции и Self.Generator.Element - это тип объектов, например, для массива [Int?] Self.Generator.Element будет Int.

В целом этот метод фильтра может применяться к любому типу CollectionType, ему нужен блок фильтров, который берет элемент коллекции и возвращает Bool, и возвращает массив исходного типа. Поэтому, комбинируя этот метод, я считаю полезным: он объединяет карту и фильтр, беря блок, который сопоставляет элемент коллекции с необязательным значением, и возвращает массив из тех необязательных значений, которые не равны нулю.

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

Ответ 7

import Foundation

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

Ответ 8

(Swift 2.x)

Вы также можете расширить массив, чтобы он соответствовал протоколу, содержащему синие-rpints для методов общего типа, например, протокол, содержащий ваши пользовательские функциональные утилиты для всех общих элементов массива, соответствующих некоторому ограничению типа, например, протокол MyTypes. Бонус, использующий этот подход, заключается в том, что вы можете писать функции, принимающие общие аргументы массива, с ограничением того, что эти аргументы массива должны соответствовать вашему протоколу специальных утилит, например, протоколу MyFunctionalUtils.

Вы можете получить это поведение либо неявно, либо путем ограничения элементов массива на MyTypes, либо --- как я покажу в описанном ниже методе ---, довольно аккуратно, явно, позволяя вашим общим функциям массива header, показывают, что входные массивы соответствуют MyFunctionalUtils.


Начнем с протоколов MyTypes для использования в качестве ограничения типа; расширьте типы, которые вы хотите поместить в ваши дженерики по этому протоколу (пример ниже расширяет основные типы Int и Double, а также настраиваемый тип 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)
}

Протокол MyFunctionalUtils (содержащий чертежи наших дополнительных функций общих функций массива), а затем расширение массива на MyFunctionalUtils; реализация синего печатного метода (ов):

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

Наконец, тесты и два примера, показывающие функцию, принимающую общие массивы, со следующими случаями, соответственно

  • Отображение неявного утверждения о том, что параметры массива соответствуют протоколу MyFunctionalUtils, посредством типа, ограничивающего элементы массивов на "MyTypes" (функция bar1).

  • Очевидно, что параметры массива соответствуют протоколу MyFunctionalUtils (функция bar2).

Ниже приведен тест и примеры:

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

Ответ 9

Расширить все типы:

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

Расширить определенный тип:

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

Ответ 10

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
        }

    }

}