Search code examples
arraysswiftswift-extensions

Swift Extension of Array with Equatable Elements Cannot Call Index(of:)


I am attempting to add an extension to the Array type in Swift limited to Arrays whose elements conform to the equatable protocol. I am attempting to define a function in the following manner:

import Foundation

extension Array where Iterator.Element: Equatable {

    func deletedIndicies<T: Equatable>(newArray: [T]) -> [Int] {

        var indicies = [Int]()

        for element in self {

              if newArray.index(of: element) == nil {

                     indicies.append(self.index(of: element)!)
              }
        }

        return indicies
        }
    }
}

The purpose of the function is to return the indices of any items in the original array that do not appear in the newArray.

The error I receive in Xcode is: Cannot invoke 'index' with an argument list of type '(of: Element)'

Since I am defining the function for only Arrays whose elements are equatable and am requiring that the elements of the newArray are equatable, I am unsure why I cannot invoke the index method.


Solution

  • The problem is you're defining a new generic placeholder T in your method – which is not necessarily the same type as Element. Therefore when you say newArray.index(of: element), you're trying to pass an Element into a argument of type T.

    The solution therefore is to simply to type the newArray: parameter as [Element]:

    extension Array where Element : Equatable {
        func deletedIndicies(byKeeping elementsToKeep: [Element]) -> [Int] {
            // ...
        }
    }
    

    As a side note, this method could also be implemented as:

    extension Array where Element : Equatable {
    
        func deletedIndicies(byKeeping elementsToKeep: [Element]) -> [Int] {
    
            // use flatMap(_:) to iterate over a sequence of pairs of elements with indices,
            // returning the index of the element, if elementsToKeep doesn't contains it,
            // or nil otherwise, in which case flatMap(_:) will filter it out.
            return self.enumerated().flatMap {
                elementsToKeep.contains($1) ? nil : $0
            }
        }
    }
    

    Also, if you change the constraint on Element to Hashable, this could be also be implemented in O(n), rather than O(n * m) time, which could potentially be desirable:

    extension Array where Element : Hashable {
    
        func deletedIndicies(byKeeping elementsToKeep: [Element]) -> [Int] {
    
            // create new set of elements to keep.
            let setOfElementsToKeep = Set(elementsToKeep)
    
            return self.enumerated().flatMap {
                setOfElementsToKeep.contains($1) ? nil : $0
            }
        }
    }