Как я могу расширить типизированные массивы в 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
}
}
}