Swift中数组内的唯一对象

12

我有一个包含自定义对象的数组。

我想弹出重复的对象,具有重复的属性:

let product = Product()
product.subCategory = "one"

let product2 = Product()
product2.subCategory = "two"

let product3 = Product()
product3.subCategory = "two"

let array = [product,product2,product3]

在这种情况下,弹出product2或product3


你所说的“pop”是指移除吗?也就是说你想要从数组中移除重复的对象? - Duncan C
https://dev59.com/xmAg5IYBdhLWcg3wBXKs#33207005 - Leo Dabus
6个回答

24

下面是一个数组扩展,可以根据给定的键返回对象唯一列表:

extension Array {
    func unique<T:Hashable>(map: ((Element) -> (T)))  -> [Element] {
        var set = Set<T>() //the unique list kept in a Set for fast retrieval
        var arrayOrdered = [Element]() //keeping the unique list of elements but ordered
        for value in self {
            if !set.contains(map(value)) {
                set.insert(map(value))
                arrayOrdered.append(value)
            }
        }

        return arrayOrdered
    }
}

使用这个,你可以做到这一点。
let unique = [product,product2,product3].unique{$0.subCategory}

这样做的优点是不需要Hashable,并且可以基于任何字段或组合返回唯一的列表。

根据API设计准则,这个方法应该被称为“uniqueing”,并且可以编写一个变异的变体“unique”。但我认为这有点奇怪,所以我会推荐使用“removingDuplicates”和“removeDuplicates”作为一对方法名称。-https://swift.org/documentation/api-design-guidelines/#strive-for-fluent-usage - calql8edkos

10
您可以使用Swift的Set
let array = [product,product2,product3]

let set = Set(array)

你需要使 Product 符合 Hashable(从而符合 Equatable):

class Product : Hashable {
    var subCategory = ""

    var hashValue: Int { return subCategory.hashValue }
}

func ==(lhs: Product, rhs: Product) -> Bool {
    return lhs.subCategory == rhs.subCategory
}

而且,如果Product是一个NSObject的子类,您必须重写isEqual

override func isEqual(object: AnyObject?) -> Bool {
    if let product = object as? Product {
        return product == self
    } else {
        return false
    }
}

显然,将其修改以反映您在类中可能具有的其他属性。例如:

class Product : Hashable {
    var category = ""
    var subCategory = ""

    var hashValue: Int { return [category, subCategory].hashValue }
}

func ==(lhs: Product, rhs: Product) -> Bool {
    return lhs.category == rhs.category && lhs.subCategory == rhs.subCategory
}

3
使用 set 数据结构不会保留数组顺序,需要注意。 - Duncan C
@Rob 我正在使用 RLMObject(它是 NSObject,因此符合 Equatable 和 Hashable)。尝试重写 var hashValue: Int { return subCategoria.hashValue },但我只得到错误“无法使用类型 '([Product])' 的参数列表调用类型为'Set <_>'的初始化程序”。 - Vinícius Albino
嗯。如果“Product”没有符合“Hashable”的要求,那么您会得到这个错误。您可以尝试明确声明它(或扩展它)符合“Hashable”的要求。 - Rob
使用重写(override)hashValue声明应该可以解决这个问题,对吗? 因为如果我试图显式地遵循协议Hashable,我会得到Redundant conformance of 'Product' to protocol 'Hashable'的错误提示。 - Vinícius Albino
1
@user1108474 我刚刚自己尝试了一下,只使用了一些超类和子类。override hasValue 应该可以工作。你也在实现 Equatable 函数吗? - R Menke
2
@user1108474 - 如果它已经符合Hashable,那么您不应该声明再次符合。只是没有遵守Hashable是我能够重现您与我们分享的错误的唯一方法。无论如何,我使用基于RLMObject的“Product”进行了测试,并且它运行良好(虽然我还必须覆盖isEqual,就像修订后的答案所示)。 - Rob

2
如果 Product 符合 Equatable 协议,其中一个产品基于其子类别相等(而您不关心顺序),则可以将对象添加到集合中,并从该集合中获取数组:
let array = [product,product2,product3]
let set = NSSet(array: array)
let uniqueArray = set.allObjects

或者

let array = [product,product2,product3]
let set = Set(array)
let uniqueArray = Array(set)

我正在撰写我的回答,尝试了您甜美的 Set 解决方案。但它在 Array 的扩展中不起作用 :/ - R Menke
找到了,对于基于集合的解决方案,对象需要是“可哈希的”。 - R Menke
@RMenke 哦,没错,我假设该对象符合 NSObject,它符合 EquatableHashable - JAL

1
class Product {
    var subCategory: String = ""
}

let product = Product()
product.subCategory = "one"

let product2 = Product()
product2.subCategory = "two"

let product3 = Product()
product3.subCategory = "two"

let array = [product,product2,product3]

extension Product : Hashable {
    var hashValue: Int {
        return subCategory.hashValue
    }
}
func ==(lhs: Product, rhs: Product)->Bool {
    return lhs.subCategory == rhs.subCategory
}

let set = Set(array)
set.forEach { (p) -> () in
    print(p, p.subCategory)
}
/*
Product one
Product two
*/

一个物品是否属于集合与其哈希值无关,而与比较有关。如果您的产品符合Hashable,则应符合Equatable。如果您需要仅基于子类别创建集合,则比较应仅取决于子类别。如果您需要以其他方式比较您的产品,则可能会遇到大麻烦。


1
如果您的类符合Hashable协议,并且希望保留原始数组顺序,可以按照以下方式创建扩展:
extension Array where Element: Hashable {
    var uniqueElements: [Element] {
        var elements: [Element] = []
        for element in self {
            if let _ = elements.indexOf(element) {
                print("item found")
            } else {
                print("item not found, add it")
                elements.append(element)
            }
        }
        return elements
    }
}

1

这是一个基于KeyPath的版本,参考了Ciprian Rarau的解决方案

extension Array {
    func unique<T: Hashable>(by keyPath: KeyPath<Element, T>) -> [Element] {
        var set = Set<T>()
        return self.reduce(into: [Element]()) { result, value in
            guard !set.contains(value[keyPath: keyPath]) else {
                return
            }
            set.insert(value[keyPath: keyPath])
            result.append(value)
        }
    }
}

使用示例:

let unique = [product, product2, product3].unique(by: \.subCategory)

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