110

I am trying to reduce an array of objects to a set in Swift and this is my code:

objects.reduce(Set<String>()) { $0.insert($1.URL) }

However, I get an error:

Type of expression is ambiguous without more context.

I do not understand what the problem is, since the type of URL is definitely String. Any ideas?

shim
  • 9,289
  • 12
  • 69
  • 108
Banana
  • 4,010
  • 9
  • 33
  • 49
  • I think the signature for reduce is `func reduce(_ initial: T, @noescape combine combine: (T, Self.Generator.Element) throws -> T) rethrows -> T` which is not what you're passing. – Martin Marconcini Dec 08 '15 at 17:15

4 Answers4

182

You don't have to reduce an array to get it into a set; just create the set with an array: let objectSet = Set(objects.map { $0.URL }).

NRitH
  • 13,441
  • 4
  • 41
  • 44
39

With Swift 5.1, you can use one of the three following examples in order to solve your problem.


#1. Using Array's map(_:) method and Set's init(_:) initializer

In the simplest case, you can map you initial array to an array of urls (String) then create a set from that array. The Playground below code shows how to do it:

struct MyObject {
    let url: String
}

let objectArray = [
    MyObject(url: "mozilla.org"),
    MyObject(url: "gnu.org"),
    MyObject(url: "git-scm.com")
]

let urlArray = objectArray.map({ $0.url })
let urlSet = Set(urlArray)
dump(urlSet)
// ▿ 3 members
//   - "git-scm.com"
//   - "mozilla.org"
//   - "gnu.org"

#2. Using Array's reduce(into:_:) method

struct MyObject {
    let url: String
}

let objectArray = [
    MyObject(url: "mozilla.org"),
    MyObject(url: "gnu.org"),
    MyObject(url: "git-scm.com")
]

let urlSet = objectArray.reduce(into: Set<String>(), { (urls, object) in
    urls.insert(object.url)
})
dump(urlSet)
// ▿ 3 members
//   - "git-scm.com"
//   - "mozilla.org"
//   - "gnu.org"

As an alternative, you can use Array's reduce(_:_:) method:

struct MyObject {
    let url: String
}

let objectArray = [
    MyObject(url: "mozilla.org"),
    MyObject(url: "gnu.org"),
    MyObject(url: "git-scm.com")
]

let urlSet = objectArray.reduce(Set<String>(), { (partialSet, object) in
    var urls = partialSet
    urls.insert(object.url)
    return urls
})
dump(urlSet)
// ▿ 3 members
//   - "git-scm.com"
//   - "mozilla.org"
//   - "gnu.org"

#3. Using an Array extension

If necessary, you can create a mapToSet method for Array that takes a transform closure parameter and returns a Set. The Playground below code shows how to use it:

extension Array {

    func mapToSet<T: Hashable>(_ transform: (Element) -> T) -> Set<T> {
        var result = Set<T>()
        for item in self {
            result.insert(transform(item))
        }
        return result
    }

}

struct MyObject {
    let url: String
}

let objectArray = [
    MyObject(url: "mozilla.org"),
    MyObject(url: "gnu.org"),
    MyObject(url: "git-scm.com")
]

let urlSet = objectArray.mapToSet({ $0.url })
dump(urlSet)
// ▿ 3 members
//   - "git-scm.com"
//   - "mozilla.org"
//   - "gnu.org"
Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
5

reduce() method expects a closure that returns a combined value, while insert() methods of Set value does not return anything but instead it inserts a new element into the existing set.

In order to make it work you would need to do something like:

objects.reduce(Set<String>()) {
    $0.union(CollectionOfOne($1.URL))
}

But the above is a bit of an unnecessary complication. If you have a big array, that would mean quite a number of ever-growing sets to be created while Swift goes over all the elements from objects. Better follow the advice from @NRitH and use map() as that would make a resulting set in one go.

0x416e746f6e
  • 9,872
  • 5
  • 40
  • 68
  • Thanks for this answer, it clarifies the problem for me. – Banana Dec 08 '15 at 17:34
  • @Banana, np. See my edited answer. It turns out to be not only over-complicating but also an inefficient thing to do. – 0x416e746f6e Dec 08 '15 at 17:52
  • This is a really bad solution. Each time you creates a new set instanse. https://developer.apple.com/documentation/swift/set/3128858-union . Union Returns a new set. – Vyacheslav Oct 17 '20 at 11:54
1

Swift 1.0-2.x ONLY:

If URL on your object is a strongly-typed String, you can create a new Set<String> object and use unionInPlace on the set with the mapped array:

var mySet = Set<String>()
mySet.unionInPlace(objects.map { $0.URL as String })
JAL
  • 41,701
  • 23
  • 172
  • 300