Як я можу розширити набрані масиви в Swift?


203

Як я можу розширити Swift Array<T>або T[]набрати спеціальними функціональними утилітами?

Перегляд документів API Swift показує, що методи 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', тобто:

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

І коли ступінчаста через налагоджувач вказує на тип, Swift.Stringале це помилка побудови, щоб спробувати отримати доступ до нього, як String, не вводячи його Stringспочатку, тобто:

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

Хтось знає, що є правильним способом створення набраного методу розширення, який діє як вбудовані розширення?


Проголосували, тому що я також не можу знайти відповіді. Бачити той самий extension T[]біт при натисканні на команду типу Array у XCode, але не бачити жодного способу його реалізації без отримання помилки.
ім’я користувача tbd

@usernametbd FYI щойно знайшов це, схоже, рішення було усунути <T>з підпису методу.
mythz

Відповіді:


296

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

class HighScoreEntry {
    let score:Int
}

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

Спроба зробити це за допомогою структури або 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 
{
    // ...
}

1
компілятор повідомляє, що "SequenceType" було перейменовано на "Послідовність"
sandover

Чому ви не використовували Iterator.Element у типі повернення [Iterator.Element]?
gaussblurinc

1
привіт, чи можете ви пояснити функцію умовного відповідності в 4.1? Що нового в 4.1? Ми могли це зробити в 2.2? Що мені не вистачає
osrl

З Swift 3.1 ви можете розширити масиви з некласами із наступним синтаксисом: розширення Array, де Element == Int
Giles

63

Через деякий час, пробуючи різні речі, рішення, здається, видаляє <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 }

1
BTW Те, що ви тут визначили, функціонально еквівалентно існуючій filterфункції:let x = ["A","B","C","X”].filter { $0.compare("A") > 0 }
Палімондо,


4
Я бачу. Подвійна фільтрація здається досить глючний до мене ... Але він по- , як і раніше вважає , що filterце функціонально еквівалентно до вашого find, тобто результат функції та ж. Якщо ваше закриття фільтра має побічні ефекти, результати, можливо, не сподобаються.
Палімондо

2
@Palimondo Точно фільтр за замовчуванням має несподівану поведінку, тоді як вищевказаний impl працює як очікувалося (і чому він існує). Це не є функціонально еквівалентним, якщо він виконує закриття двічі, що може потенційно мутувати змінні масштабування (це трапилось помилку, в яку я зіткнувся, звідси питання про його поведінку). Також зверніть увагу на питання, яке конкретно згадує про те, що хочеться замінити вбудований Swift filter.
mythz

4
Ми ніби сперечаємось щодо визначення слова функціональний . Звично, в функціональної парадигми програмування , де filter, mapі reduceфункції виходять з функції виконуються для їх значень, що повертаються. На противагу цьому, eachвизначена вище функція є прикладом функції, виконаної для її побічного ефекту, оскільки вона нічого не повертає. Напевно, ми можемо погодитися, що поточна реалізація Swift не є ідеальною, і в документації нічого не зазначено про її характеристики виконання.
Палімондо

24

Розширити всі типи:

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

Розширення деяких типів:

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

Розширити певний тип:

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

8

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

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

Ключовим для нього було слово "Елемент". Зауважте, що я ніде не визначав цей тип, здається, що він автоматично існує в контексті розширення масиву і посилається на будь-який тип елементів масиву.

Я не на 100% впевнений, що там відбувається, але думаю, що це, мабуть, тому, що "Element" є асоційованим типом масиву (див. "Асоційовані типи" тут 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 ) ... тому я ще трохи не впевнений.


1
Arrayє загальним типом: Array<Element>(див. swiftdoc.org/v2.1/type/Array ), Elementє заповнювачем для міститься типу. Наприклад: var myArray = [Foo]()означає, що myArrayбуде містити лише тип Foo. Fooу цьому випадку "відображається" на загальний заповнювач Element. Якщо ви хочете змінити загальну поведінку Array (через розширення), ви використовуєте загальний заповнювач, Elementа не будь-який конкретний тип (наприклад, Foo).
Девід Джеймс

5

Використання 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"]

Це також може бути досягнуто, за допомогою let deDuped = Set(dupes)якого ви могли б повернутися неруйнівним методом, який називається до toSetтих пір, поки ви все не будете зі зміною типу
alexpyoung

@alexpyoung ви б зіпсували порядок масиву, якщо зробите Set ()
Danny Wang

5

Якщо ви хочете дізнатися про розширення масивів та інші типи збірки в класах, коди оформлення замовлення в цьому github repo https://github.com/ankurp/Cent

З Xcode 6.1 синтаксис для розширення масивів виглядає наступним чином

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

1
@Rob Оновлено URL
Encore PTL

3

Я ознайомився зі стандартними заголовками бібліотеки 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
    }
}

2
import Foundation

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

0

( Швидкий 2.x )

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

Ви можете отримати таку поведінку або неявно, ввівши обмеження елементів масиву до MyTypes, або --- як я покажу в описаному нижче методі ---, досить акуратно, явно, дозволяючи заголовку загальної функції масиву безпосередньо показувати, що вхідні масиви відповідає 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(тримає креслення наших додаткових загальних функцій масиву утиліти) і далі, розширення Array на 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
    }
}

Нарешті, тести та два приклади, що показують функцію, що приймає загальні масиви, з наступними випадками відповідно

  1. Показано неявне твердження, що параметри масиву відповідають протоколу "MyFunctionalUtils", використовуючи тип обмеження елементів масивів на "MyTypes" (функція bar1).

  2. Показано явно , що параметри масиву відповідають протоколу «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

-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
Ці збитки ( $0 as! Double) борються проти системи типу Свіфт, а також, на мій погляд, перемагають мету питання ОП. Тим самим ви втрачаєте будь-який потенціал для оптимізації компілятора для обчислень, які ви насправді хочете зробити, і ви також забруднюєте простір імен Array безглуздими функціями (чому б ви хотіли бачити .calculateMedian () у масиві UIViews або нічого, крім подвійного в цьому питанні?). Є кращий спосіб.
ефемер

спробуйтеextension CollectionType where Generator.Element == Double {}
ephemer
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.