Home > Software engineering >  How can I pass in any function as a parameter?
How can I pass in any function as a parameter?

Time:11-19

I'm trying to make an extension to Array, see below

extension Array {
    func mapThenUnique<T: Comparable>(f: (Element) -> T) -> Array<T> {
        var mapped: Array<T> = Array(self.filter( f(self) ))
        /* Three Errors /\
        1) Cannot assign value of type '[Element]' to type 'Array<T>'
         2) Cannot convert value of type 'Array<Element>' to expected argument type 'Element'
          3) Cannot convert value of type 'T' to expected argument type '(Element) throws -> Bool' */
        var noDups: Array<T> = []
        for element in mapped {
            if (!noDups.contains(where: element)) { noDups.append(element) }
            // Error - Cannot convert value of type 'T' to expected argument type '(T) throws -> Bool'
        }
        return noDups
    }
}

I know the noDups array has to be of type 'T' because that is the correct return type, see below for an example of what mapThenUnique does:

["abc", "Hi", "AbC"].mapThenUnique { $0.lowercased() } -> ["abc", "hi"]
[2, 9, -9, 3].mapThenUnique { Int($0) * $0 } -> [4, 9, 81]
// notice that there are no duplicates in the array

But I'm not sure why I am getting these error messages. And what does '(Element) throws -> Bool' mean? Any help will be greatly appreciated!

Edit: My now awake brain realizes that it should be map instead of filter, thanks @Sulthan.

extension Array {
    func mapThenUnique<T: Comparable>(f: (Element) -> T) -> Array<T> {
        var mapped: Array<T> = self.map{ f($0) }
        var noDups: Array<T> = []
        for element in filtered {
            if (!noDups.contains(where: element)) { noDups.append(element) }
            // Error - Cannot convert value of type 'T' to expected argument type '(T) throws -> Bool'
        }
        noDups.sort()
        // this works for the numerical test cases but not for ["abc", "Hi", "AbC"] which returns ["abc", "hi"], idk why it does
        return noDups
    }
}

I'm still trying to figure out what the error message in the for loop means. Oh, this is for a HW assignment.

CodePudding user response:

If you use a Set and make it Hashable instead of Comparable, it's fairly straightforward

extension Array {
    func mapThenUnique<T: Hashable>(f: (Element) -> T) -> [T] {
        var seen = Set<T>()
        return map { f($0) }.filter { seen.insert($0).inserted }
    }
}

["abc", "Hi", "AbC"].mapThenUnique { $0.lowercased() } // ["abc", "hi"]

[2, 9, -9, 3].mapThenUnique { Int($0) * $0 } // [4, 81, 9]

Or if you don't care about the original order

Set(["abc", "Hi", "AbC"].map { $0.lowercased() }) // ["abc", "hi"] or ["hi", "abc"]

Set([2, 9, -9, 3].map { Int($0) * $0 }) // Some random order of [4, 81, 9]

Also a more "correct" way to do this in Swift would be to create the extension on Collection

extension Collection where Element: Hashable {
    func mapThenUnique(f: (Element) -> Element) -> [Element] {
        var seen = Set<Element>()
        return map { f($0) }.filter { seen.insert($0).inserted }
    }
}

Although I consider it "bad form" to have a function that does two things, so my personal preference would be

extension Collection where Element: Hashable {
    func unique() -> [Element] {
        var seen = Set<Element>()
        return filter { seen.insert($0).inserted }
    }
}

["abc", "Hi", "AbC"].map { $0.lowercased() }.unique()

(Again, assuming you want to keep the order. Otherwise, just use a Set!)

  • Related