在Swift中将数组转换为集合

110
我正试图在Swift中将一个对象数组缩减为一组。以下是我的代码:

我正试图在Swift中将一个对象数组缩减为一组。以下是我的代码:

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

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

在没有更多上下文的情况下,表达式类型不明确。

我不理解问题出在哪里,因为URL的类型肯定是字符串。有任何想法吗?


我认为reduce的签名是func reduce<T>(_ initial: T, @noescape combine combine: (T, Self.Generator.Element) throws -> T) rethrows -> T,这不是你正在传递的内容。 - Martin Marconcini
4个回答

184

你不需要将数组缩小以使其成为集合;只需使用数组创建集合:let objectSet = Set(objects.map { $0.URL })


但我不想要对象的集合,而是这些对象拥有的不同URL的集合。我猜我可以用map然后Set(map),但我应该能够通过reduce实现这一点。 - Banana
1
啊。那么 let objectSet = Set(objects.map { $0.URL }) - NRitH
啊,好的,太棒了。你能否编辑一下你的答案,这样我就可以接受它了吗? - Banana
1
这要看情况。如果你使用map/flatmap,链式调用会更好。 - pronebird

40

使用 Swift 5.1,您可以使用以下三个示例中的任何一个来解决您的问题。


#1. 使用 Arraymap(_:) 方法和 Setinit(_:) 初始化程序

在最简单的情况下,您可以将初始数组映射到一个urlString )数组,然后从该数组创建一个集合。下面的代码展示了如何完成:

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. 使用Arrayreduce(into:_:)方法


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"

作为替代方案,您可以使用Arrayreduce(_:_:)方法:

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. 使用 Array 扩展

如果需要,您可以为 Array 创建一个 mapToSet 方法,该方法接受一个 transform 闭包参数并返回一个 Set。下面的示例演示了如何使用它:

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"

5

reduce()方法需要接收一个闭包,该闭包返回一个合并后的值,而Setinsert()方法不返回任何东西,而是将新元素插入到现有集合中。

为了使其正常工作,您需要执行以下操作:

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

但是上面的方法有些不必要的复杂。如果你有一个大数组,那么在Swift遍历所有元素时,就需要创建相当多的不断增长的集合。最好按照@NRitH的建议使用map(),这样可以一次性生成结果集。


谢谢您的回答,它为我澄清了这个问题。 - Banana
@Banana,没问题。看看我编辑过的答案。事实证明这不仅过于复杂,而且效率低下。 - 0x416e746f6e
这是一个非常糟糕的解决方案。每次创建新的集合实例。https://developer.apple.com/documentation/swift/set/3128858-union。Union返回一个新的集合。 - Vyacheslav

1

仅适用于Swift 1.0-2.x:

如果您的对象上的URL是强类型String,则可以创建一个新的Set<String>对象,并使用unionInPlace将映射数组与集合合并:

var mySet = Set<String>()
mySet.unionInPlace(objects.map { $0.URL as String })

这应该是 formUnionunionInPlace 不是 Swift 的一部分。 - Drew
嘿@Drew,这个答案是在2015年12月写的,当时unionInPlace是Swift 2的一部分。 - JAL

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接