Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I extend typed Arrays in Swift?

Tags:

arrays

swift

People also ask

How do you write an array extension in Swift?

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).

Can you append an array to an array Swift?

To append another Array to this Array in Swift, call append(contentsOf:) method on this array, and pass the other array for contentsOf parameter. append(contentsOf:) method appends the given array to the end of this array.

How do you extend a string in Swift?

Creating an extension in Swift Creating extensions is similar to creating named types in Swift. When creating an extension, you add the word extension before the name. extension SomeNamedType { // Extending SomeNamedType, and adding new // functionality to it. }


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 
{
    // ...
}

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 }

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 {
    // ...
}

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.


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

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

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