在 Swift 中设置减少数组

我试图在 Swift 中将一个对象数组简化为一个集合,下面是我的代码:

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

然而,我得到了一个错误:

如果没有更多的上下文,表达式的类型是不明确的。

我不明白问题是什么,因为 URL 的类型肯定是字符串。有什么想法吗?

89706 次浏览

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

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.

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

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"