-2

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.

Andrew
  • 26,706
  • 9
  • 85
  • 101
  • 2
    Well, you are obviously trying to `map`, so why are you using `filter`? Use `map`, that will probably fix all errors. Nevertheless, this kind of extension does not make much sense. How is that better than `array.map { $0.lowercased() }.unique()`? – Sulthan Nov 18 '21 at 20:20
  • And use `contains(element)` instead of `contains(where: element)` – Joakim Danielson Nov 18 '21 at 20:25
  • 1
    @Sulthan `unique()`? Did you mean [this](https://stackoverflow.com/a/27624444/9223839) method? – Joakim Danielson Nov 18 '21 at 20:30
  • @Sulthan Thanks! That did fix the errors on that line but I'm still confused why its giving me an error in the for loop. – ThreeRingBinder Nov 18 '21 at 21:07
  • You have remove capitalization in the `map` call, thus, you cannot expect them to be present when sorting. The whole thing should be split to simpler tasks. First define `unique((Element, Element) -> Bool) -> [Element]` to which you can pass a method that decides whether two elements are equal. Then you can simply call `array.unique { $0.lowercased() == $1.lowercased() }.sorted()`. – Sulthan Nov 18 '21 at 21:31
  • Please leave your question as a question. – Andrew Nov 18 '21 at 23:38

1 Answers1

1

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

Claus Jørgensen
  • 25,882
  • 9
  • 87
  • 150