251

How can I extend Swift's Array<T> or T[] type with custom functional utils?

Browsing around Swift's API docs shows that Array methods are an extension of the T[], e.g:

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

    var count: Int { get }

    var capacity: Int { get }

    var isEmpty: Bool { get }

    func copy() -> T[]
}

When copying and pasting the same source and trying any variations like:

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

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

It fails to build with the error:

Nominal type T[] can't be extended

Using the full type definition fails with Use of undefined type 'T', i.e:

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

And it also fails with Array<T : Any> and Array<String>.

Curiously Swift lets me extend an untyped array with:

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

Which it lets me call with:

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

But I can't create a proper generic type extension as the type seems to be lost when it flows through the method, e.g trying to replace Swift's built-in filter with:

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

But the compiler treats it as untyped where it still allows calling the extension with:

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

And when stepped-thru with a debugger indicates the type is Swift.String but it's a build error to try access it like a String without casting it to String first, i.e:

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

Does anyone know what's the proper way to create a typed extension method that acts like the built-in extensions?

Community
  • 1
  • 1
mythz
  • 141,670
  • 29
  • 246
  • 390
  • Voted up because I can't find an answer myself either. Seeing the same `extension T[]` bit when Command-clicking on the Array type in XCode, but not seeing any way to implement it without getting an error. – username tbd Jun 04 '14 at 02:37
  • @usernametbd FYI just found it, looks like the solution was to remove `` from the method signature. – mythz Jun 04 '14 at 02:53

11 Answers11

373

For extending typed arrays with classes, the below works for me (Swift 2.2). For example, sorting a typed array:

class HighScoreEntry {
    let score:Int
}

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

Trying to do this with a struct or typealias will give an error:

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

Update:

To extend typed arrays with non-classes use the following approach:

typealias HighScoreEntry = (Int)

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

In Swift 3 some types have been renamed:

extension Sequence where Iterator.Element == HighScoreEntry 
{
    // ...
}
Ramis
  • 13,985
  • 7
  • 81
  • 100
Andrew Schreiber
  • 14,344
  • 6
  • 46
  • 53
  • 1
    compiler reports that 'SequenceType' has been renamed to 'Sequence' – sandover Apr 05 '16 at 04:23
  • 1
    Why you didn't use Iterator.Element in return type `[Iterator.Element]`? – gaussblurinc Oct 01 '16 at 19:22
  • 1
    hi, can you explain the Conditional Conformance feature in 4.1? What's new in 4.1? We could do that in 2.2? What am I missing – osrl Apr 11 '18 at 11:37
  • Since Swift 3.1 you can extend arrays with non-classes with the following syntax: extension Array where Element == Int – Giles Oct 04 '18 at 08:26
68

After a while trying different things the solution seems to remove the <T> from the signature like:

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

Which now works as intended without build errors:

["A","B","C"].find { $0.compare("A") > 0 }
mythz
  • 141,670
  • 29
  • 246
  • 390
  • 1
    BTW What you’ve defined here is functionally equivalent to the existing `filter` function: `let x = ["A","B","C","X”].filter { $0.compare("A") > 0 }` – Palimondo Jun 13 '14 at 00:10
  • 6
    @Palimondo No it's not, [the built-in filter executes callbacks twice](http://stackoverflow.com/q/24025633/85785). – mythz Jun 13 '14 at 01:02
  • 4
    I see. Double filtering seems rather buggy to me... But it still holds that the `filter` is **functionally equivalent** to your `find`, i.e. the result of the function is the same. If your filter closure has side-effects, you might not like the results, for sure. – Palimondo Jun 16 '14 at 09:29
  • 2
    @Palimondo Exactly, the default filter has unexpected behavior whereas the above find impl works as expected (and why it exists). It's not functionally equivalent if it executes the closure twice, which can potentially mutate scoped variables (that happened to be bug I ran into, hence the question on its behavior). Also note the question specifically mentions wanting to replace Swift's built-in `filter`. – mythz Jun 16 '14 at 09:41
  • 4
    We seem to be arguing over the definition of the word *functional*. Customarily, in functional programming paradigm where the `filter`, `map` and `reduce` functions originate from, functions are executed for their return values. To contrast, the `each` function you define above is an example of a function executed for its side-effect, because it returns nothing. I guess we can agree that the current Swift implementation is not ideal and the documentation does not state anything about its runtime characteristics. – Palimondo Jun 16 '14 at 09:51
  • 1
    It doesn't look to me like it's necessary to cast x as T so, presuming that's true, you can dispense with the let statement and simply refer directly to x. The semicolon at the end of the let also wasn't necessary. Since, as of beta 5, += is only used to concatenate an array, you'll now have to use append to add elements qualified elements to the array being returned. – Bruce Hobbs Aug 08 '14 at 04:14
54

Extend all types:

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

Extend Comparable types:

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

Extend some types:

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

Extend a particular type:

extension Array where Element == Int {
    // ...
}
Andrew_STOP_RU_WAR_IN_UA
  • 9,318
  • 5
  • 65
  • 101
Dmitry
  • 14,306
  • 23
  • 105
  • 189
10

I had a similar problem - wanted to extend the general Array with a swap() method, which was supposed to take an argument of the same type as the array. But how do you specify the generic type? I found by trial and error that the below worked:

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

The key to it was the word 'Element'. Note that I didn't define this type anywhere, it seems automatically exist within the context of the array extension, and refer to whatever the type of the array's elements is.

I am not 100% sure what's going on there, but I think it is probably because 'Element' is an associated type of the Array (see 'Associated Types' here https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Generics.html#//apple_ref/doc/uid/TP40014097-CH26-ID189)

However, I can't see any reference of this in the Array structure reference (https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_Array_Structure/index.html#//apple_ref/swift/struct/s:Sa)... so I'm still a little unsure.

Daniel Howard
  • 4,330
  • 3
  • 30
  • 23
  • 1
    `Array` is a generic type: `Array` (see http://swiftdoc.org/v2.1/type/Array/), `Element` is a placeholder for the contained type. For example: `var myArray = [Foo]()` means that `myArray` will only contain type `Foo`. `Foo` in this case is "mapped" to the generic placeholder `Element`. If you want to change the general behavior of Array (via extension) you would use the generic placeholder `Element` and not any concrete type (like Foo). – David James Mar 09 '16 at 14:32
7

Using Swift 2.2: I ran into a similar issue when trying to remove duplicates from an array of strings. I was able to add an extension on the Array class that does just what I was looking to do.

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

Adding these two methods to the Array class allows me to call one of the two methods on an array and successfully remove duplicates. Note that the elements in the array must conform to the Hashable protocol. Now I can do this:

 var dupes = ["one", "two", "two", "three"]
 let deDuped = dupes.removeDuplicates()
 dupes.removeDuplicatesInPlace()
 // result: ["one", "two", "three"]
James
  • 323
  • 3
  • 8
  • This could also be accomplished with `let deDuped = Set(dupes)`, which you could return in a non-destructive method called `toSet` as long as you're ok with the type change – alexpyoung Aug 02 '16 at 03:48
  • @alexpyoung you would mess up the order of the array if you do Set() – Danny Wang Feb 24 '20 at 23:48
5

If you want to learn about extending Arrays and other types of build in classes checkout code in this github repo https://github.com/ankurp/Cent

As of Xcode 6.1 the syntax to extend arrays is as follows

extension Array {
    func at(indexes: Int...) -> [Element] {
        ... // You code goes herer
    }
}
Encore PTL
  • 8,084
  • 10
  • 43
  • 78
3

I had a look at the Swift 2 standard library headers, and here is the prototype for the filter function, which makes it quite obvious how to roll your own.

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

It's not an extension to Array, but to CollectionType, so the same method applies to other collection types. @noescape means that the block passed in will not leave the scope of the filter function, which enables some optimisations. Self with a capital S is the class we are extending. Self.Generator is an iterator that iterates through the objects in the collection and Self.Generator.Element is the type of the objects, for example for an array [Int?] Self.Generator.Element would be Int?.

All in all this filter method can be applied to any CollectionType, it needs a filter block which takes an element of the collection and returns a Bool, and it returns an array of the original type. So putting this together, here's a method that I find useful: It combines map and filter, by taking a block that maps a collection element to an optional value, and returns an array of those optional values that are not nil.

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
  • 51,477
  • 5
  • 75
  • 98
3
import Foundation

extension Array {
    var randomItem: Element? {
        let idx = Int(arc4random_uniform(UInt32(self.count)))
        return self.isEmpty ? nil : self[idx]
    }
}
Leszek Zarna
  • 3,253
  • 26
  • 26
1

(Swift 2.x)

You can also extend the array to conform to a protocol containing blue-rpints for generic type methods, e.g., a protocol containing your custom functional utils for all generic array elements conforming to some type constraint, say protocol MyTypes. The bonus using this approach is that you can write functions taking generic array arguments, with a constraint that these array arguments must conform to your custom function utilities protocol, say protocol MyFunctionalUtils.

You can get this behaviour either implicitly, by type constraining the array elements to MyTypes, or---as I will show in the method I describe below---, quite neatly, explicitly, letting your generic array functions header directly show that input arrays conforms to MyFunctionalUtils.


We begin with Protocols MyTypes for use as type constraint; extend the types you want to fit in your generics by this protocol (example below extends fundamental types Int and Double as well as a custom type 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)
}

Protocol MyFunctionalUtils (holding blueprints our additional generic array functions utilities) and thereafter, the extension of Array by MyFunctionalUtils; implementation of blue-printed method(s):

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

Finally, tests and two examples showing a function taking generic arrays, with the following cases, respectively

  1. Showing implicit assertion that the array parameters conform to protocol 'MyFunctionalUtils', via type constraining the arrays elements to 'MyTypes' (function bar1).

  2. Showing explicitly that the array parameters conform to protocol 'MyFunctionalUtils' (function bar2).

The test and examples follows:

/* 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
dfrib
  • 70,367
  • 12
  • 127
  • 192
-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
  • 7,266
  • 4
  • 35
  • 36
  • 2
    These downcasts (`$0 as! Double`) are fighting against Swift's type system and also defeat the purpose of the OP's question, in my opinion. By doing that you're losing any potential for compiler optimisations for the calculations you actually want to do, and you're also polluting Array's namespace with meaningless functions (why would you want to see .calculateMedian() in an array of UIViews, or of anything but Double for that matter?). There is a better way. – ephemer Sep 27 '15 at 18:37
  • try `extension CollectionType where Generator.Element == Double {}` – ephemer Sep 27 '15 at 18:52
-2

Extention Array Find Index:

extension Array where Element: Equatable {
func findElementArrayIndex1(findElement: String) -> Int {
    var indexValue: Int = -1
    var search = filter { findElement.isEmpty || "\($0)".contains(findElement) }
    // print("search: \(search)")
    if search.count > 0
    {
        for i in 0 ..< count {
            if self[i] == search[0] {
                indexValue = i
                break
            }
        }
    }
    //print("indexValue -- : \(indexValue)")
    return indexValue
}}