Reduce array to set in Swift

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?

1

4 Answers

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 }).

4

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"
0

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.

3

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 })
2

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

You Might Also Like