How to Use Indices.Contains() in a Collection Extension in Swift 3

Unable to use indices.contains() in a Collection extension in Swift 3

Swift 4 Update

In Swift 4, thanks to the ability to have where clauses on associated types, Collection now enforces that Indices's Element type is the same type as the Collection's Index.

This therefore means that we can just say:

extension Collection {

/// Returns the element at the specified index iff it is within bounds, otherwise nil.
subscript (safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}

Swift 3

The Sequence protocol in Swift 3 still has a contains(_:) method, which accepts an element of the sequence if the sequence is of Equatable elements:

extension Sequence where Iterator.Element : Equatable {
// ...
public func contains(_ element: Self.Iterator.Element) -> Bool
// ...
}

The problem you're encountering is due to the change in the type of Collection's indices property requirement. In Swift 2, it was of type Range<Self.Index> – however in Swift 3, it is of type Indices (an associated type of the Collection protocol):

/// A type that can represent the indices that are valid for subscripting the
/// collection, in ascending order.
associatedtype Indices : IndexableBase, Sequence = DefaultIndices<Self>

As there's currently no way in Swift for the Collection protocol itself to express that Indices's Iterator.Element is of type Index (this will however be possible in a future version of Swift), there's no way for the compiler to know that you can pass something of type Index into contains(_:). This is because it's currently fully possible for a type to conform to Collection and implement Indices with whatever element type it wants.

Therefore the solution is to simply constrain your extension to ensure that Indices does have elements of type Index, allowing you to pass index into contains(_:):

extension Collection where Indices.Iterator.Element == Index {

/// Returns the element at the specified index iff it is within bounds, otherwise nil.
subscript (safe index: Index) -> Iterator.Element? {
return indices.contains(index) ? self[index] : nil
}
}

what's the meaning of Collection where Indices.Iterator.Element == Index

The generic constraint syntax where T == U says that the type T must be the same type as type U.

Let's do a simpler example first:

protocol GenericProtocol {
associatedtype T
associatedtype U
}

extension GenericProtocol where T == U {
func foo() {}
}

class ConcreteClassA: GenericProtocol {
typealias T = Int
typealias U = Float
}

class ConcreteClassB: GenericProtocol {
typealias T = Int
typealias U = Int
}

let a = ConcreteClassA()
let b = ConcreteClassB()

Now which of these, a or b has the member foo? The answer is b.

Since the extension's generic constraint says that T and U must be the same type, the extension is only applied to ConcreteClassB because its T and U are both Int.

Back to your code now.

In your code, you're saying that Indices.Iterator.Element must be the same type as Index. Let's figure what these two types are respectively.

Indices is the type of the property indices. So Indices.Iterator.Element is type of each index of the collection. Index on the other hand, is the type of value that you can put into the subscript of of the collection. This constraint seems excessive but it is in fact not. I can't think of an example of a type where the constraint is not true. But you can in theory create such a type. That's why the constraint is there.

If the constraint weren't there, this would not compile:

indices.contains(index)

Swift extension of Array MutableCollection won't allow reverse()

First of all, the extension should be declared like this:

extension Array where Element : MutableCollection {

You want to check that Element adheres to the protocol MutableCollection, not that it is a MutableCollection

However, then I'm not able to call the reverse method on the subscript for some reason. The best I've been able to do is this:

extension Array where Element : MutableCollection {
mutating func mirror() {
for index in self.indices {
self[index] = self[index].reversed() as! Element
}
}
}

Which works as you need it to work although the forced cast is very ugly and I dislike doing it. I suppose I should test the cast to be certain but I can't see any case where calling reversed() would result in a collection that couldn't be cast back to Element.

Edit:

I figured out the issue. The reverse() method is only valid on MutableCollection when it is also a BidirectionalCollection. This code now works correctly:

extension MutableCollection where
Iterator.Element : MutableCollection &
BidirectionalCollection,
Indices.Iterator.Element == Index {
mutating func mirror() {
for index in self.indices {
self[index].reverse()
}
}
}

Now the code should work for all MutableCollection whose elements are both a MutableCollection and BidirectionalCollection - such as [Array<Int>] or even [ArraySlice<Int>]

You can see the full code for reverse() in Swift 3.1 here:

Reverse.swift

extension MutableCollection where Self : BidirectionalCollection

Collection extension not visible when called within same extension?

Great question. My initial answer was totally wrong and you can do this.

The problem is that you've constrained the initial Collection extension with where Indices.Iterator.Element == Index but the second extension is unconstrained. You have to apply the same constraints to both. This is ok:

extension Collection where Indices.Iterator.Element == Index {
subscript (safe index: Index) -> Generator.Element? {
return indices.contains(index) ? self[index] : nil
}
}

extension Collection where Indices.Iterator.Element == Index {
func testFunc(index: Index) -> String? {
let _ = self[safe: index]
return nil
}
}


let a = [1,2,3,4,5]

a[safe: 0] // 1
a[safe: 7] // nil

Note in my testFunc() the use of self in self[safe: index]. That's mandatory. Otherwise the compiler thinks you're trying to create a dictionary with a key of safe and it can't resolve safe.

Extension for Array where Element is Optional

This is kind of tricky but you can create an AnyOptional protocol that requires an associatedtype (Wrapped) and a computed property to return the optional type. Then you can return the element unwrapped if the index is valid otherwise return nil.

protocol AnyOptional {
associatedtype Wrapped
var optional: Optional<Wrapped> { get }
}

extension Optional: AnyOptional {
var optional: Optional<Wrapped> { self }
}


extension Collection {
subscript(safe index: Index) -> Element? {
indices.contains(index) ? self[index] : nil
}
}


extension Collection  {
subscript(safe index: Index) -> Element.Wrapped? where Element: AnyOptional {
indices.contains(index) ? self[index].optional ?? nil : nil
}
}


var myArray: [String?] = ["2", "banana", nil, "31"]
var myStringArray: [String] = ["2", "3"]

let item = myArray[safe: 1] // item is String?
let strItem = myStringArray[safe: 99] // strItem is String?

Safe (bounds-checked) array lookup in Swift, through optional bindings?

Alex's answer has good advice and solution for the question, however, I've happened to stumble on a nicer way of implementing this functionality:

extension Collection {
/// Returns the element at the specified index if it is within bounds, otherwise nil.
subscript (safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}


Example

let array = [1, 2, 3]

for index in -20...20 {
if let item = array[safe: index] {
print(item)
}
}

Finding indices for all instances of element in array

You can create your own indices method that takes a predicate as parameter:

Xcode 11 • Swift 5.1

extension Collection where Element: Equatable {
func indices(of element: Element) -> [Index] { indices.filter { self[$0] == element } }
}

extension Collection {
func indices(where isIncluded: (Element) throws -> Bool) rethrows -> [Index] { try indices.filter { try isIncluded(self[$0]) } }
}

let arr = [1, 2, 3, 1, 0, 1, 2, 2, 3, 1, 1, 2]
let search = 1

let indices = arr.indices(where: { $0 == search })
// or simply
// let indices = arr.indices { $0 == search }
print(indices) // [0, 3, 5, 9, 10]

let indices2 = arr.indices(of: search)
print(indices2) // [0, 3, 5, 9, 10]

let string = "Hello World !!!"
let indices3 = string.indices(of: "o")
print(indices3) // [Swift.String.Index(_compoundOffset: 16, _cache: Swift.String.Index._Cache.character(1)), Swift.String.Index(_compoundOffset: 28, _cache: Swift.String.Index._Cache.character(1))]

Swift Collection extension: pick every other item

As other have mentioned a Dictionary is an unordered collection. If you would like to return a Dictionary you can simply extend dictionary and filter every other element. Note that there is no guarantee that the dictionary will keep the order that the key value pairs were enterer. That being said you can accomplish what you want as follow:

extension Dictionary {
var everyOtherElements: Dictionary {
var bool = true
return filter { _ in
defer { bool = !bool }
return bool
}
}
}

let dict =  ["EN" : "Cheers", "SV" : "Skåll", "ES" : "Salud" ]
// returns [(key: "EN",
let everyOtherElements = dict.everyOtherElements // ["ES": "Salud", "SV": "Skåll"]

Regarding your comment

I understand from your reply that I can't extend all collection with
the same code, I need to break it down per type?

You don't need to extend every single element type, you can for instance extend RangeReplaceableCollection protocol and return Self which will englobe Strings as well:

extension RangeReplaceableCollection  {
mutating func removeEvenIndexElements() {
var bool = true
removeAll { _ in
defer { bool = !bool }
return bool
}
}
mutating func removeOddIndexElements() {
var bool = false
removeAll { _ in
defer { bool = !bool }
return bool
}
}
func evenIndexElements() -> Self {
var bool = true
return filter { _ in
defer { bool = !bool }
return bool
}
}
func oddIndexElements() -> Self {
var bool = false
return filter { _ in
defer { bool = !bool }
return bool
}
}
}

var alphabet = "abcdefghijklmnopqrstuvwxyz"
alphabet.removeEvenIndexElements()
alphabet // "bdfhjlnprtvxz"

var arr = [1,2,3,4,5,6,7,8,9,0]
arr.removeOddIndexElements()
arr // [1, 3, 5, 7, 9]

How Can I Check My Array Index is Out of Range in Swift

It would be better to use an optional extension which returns nil if the element at the index doesn't exist.

Extension

extension Collection where Indices.Iterator.Element == Index {
subscript (optional index: Index) -> Iterator.Element? {
return indices.contains(index) ? self[index] : nil
}
}

Usage:

mannschaft18teamid = results2.data.table[optional: 17]?.team_id ?? "not existing"

How to rearrange item of an array to new position in Swift?

Swift 3.0+:

let element = arr.remove(at: 3)
arr.insert(element, at: 2)

and in function form:

func rearrange<T>(array: Array<T>, fromIndex: Int, toIndex: Int) -> Array<T>{
var arr = array
let element = arr.remove(at: fromIndex)
arr.insert(element, at: toIndex)

return arr
}

Swift 2.0:

This puts 3 into position 4.

let element = arr.removeAtIndex(3)
arr.insert(element, atIndex: 2)

You can even make a general function:

func rearrange<T>(array: Array<T>, fromIndex: Int, toIndex: Int) -> Array<T>{
var arr = array
let element = arr.removeAtIndex(fromIndex)
arr.insert(element, atIndex: toIndex)

return arr
}

The var arr is needed here, because you can't mutate the input parameter without specifying it to be in-out. In our case however we get a pure functions with no side effects, which is a lot easier to reason with, in my opinion.
You could then call it like this:

let arr = [1,2,3,4]
rearrange(arr, fromIndex: 2, toIndex: 0) //[3,1,2,4]


Related Topics



Leave a reply



Submit