如何在Swift中创建唯一对象列表的数组

59

我们如何在Swift语言中创建类似于Objective-C中的NSSetNSMutableSet的唯一对象列表。


6
为什么会被踩?Swift有NSArray和NSDictionary的相对应物,也许它也有集合的相对应物,但是它们可能没有被记录在文档中。 - Tom van der Woerdt
人们在不考虑 Swift 可能尚未有文档的情况下,会对 Swift 的问题进行负面评价。 - Nathan McKaskle
1
Swift 1.2 添加了 Set 类型。 - Gary Makin
1
我想我也应该说Swift 1.2在Xcode 6.3 beta中可用。 - Gary Makin
11个回答

54

从Swift 1.2(Xcode 6.3 beta)开始,Swift拥有了本地的集合类型。

根据发布说明:

新增了一个Set数据结构,它提供了一个独特元素的通用集合,并具有完整的值语义。它与NSSet相连,提供类似于ArrayDictionary的功能。

以下是一些简单的使用示例:

// Create set from array literal:
var set = Set([1, 2, 3, 2, 1])

// Add single elements:
set.insert(4)
set.insert(3)

// Add multiple elements:
set.unionInPlace([ 4, 5, 6 ])
// Swift 3: set.formUnion([ 4, 5, 6 ])

// Remove single element:
set.remove(2)

// Remove multiple elements:
set.subtractInPlace([ 6, 7 ])
// Swift 3: set.subtract([ 6, 7 ])

print(set) // [5, 3, 1, 4]

// Test membership:
if set.contains(5) {
    print("yes")
}

但是还有更多的方法可用。

更新:现在集合也在Swift文档的“集合类型”章节中记录下来了。


目前可以使用NSMutableSet,通过addObjectremoveObjectallObject方法来实现。原生的Set以后也是一个不错的选择。 - superarts.org

15

您可以在Swift中使用任何Objective-C类:

var set = NSMutableSet()
set.addObject(foo)

4
可以这样做,但如果你试图在其中放置一个 Swift 对象,它会抱怨它没有扩展 AnyObject - David Moles

10

Swift没有集合的概念。在Swift中使用NSMutableSet可能比使用保存虚拟值的Dictionary更慢。您可以这样做:

var mySet: Dictionary<String, Boolean> = [:]
mySet["something"]= 1

然后只需迭代键。


4
Swift中的布尔值与数字不同。你可能想用true而不是1。 - KPM
1
@KPM 你可能在想 Bool 结构体;在 Swift 中,Boolean 是 UInt8 的别名。 - lemikegao
1
在我看来,应该使用 Bool 而不是 Boolean。Bool 是 Swift 的一种数据类型,它可以存储 true / false 值,并且可以存储在字典中。 - user1046037

9

7
extension Array where Element: Hashable {
    var setValue: Set<Element> {
        return Set<Element>(self)
    }
}

let numbers = [1,2,3,4,5,6,7,8,9,0,0,9,8,7]
let uniqueNumbers = numbers.setValue    // {0, 2, 4, 9, 5, 6, 7, 3, 1, 8}

let names = ["John","Mary","Steve","Mary"]
let uniqueNames = names.setValue    // {"John", "Mary", "Steve"}

4

我认为使用带有内部字典的结构体可能是一个不错的选择。我刚开始使用它,所以尚未完全实现,而且性能也不确定。

struct Set<T : Hashable>
{
    var _items : Dictionary<T, Bool> = [:]

    mutating func add(newItem : T) {
        _items[newItem] = true
    }

    mutating func remove(newItem : T) {
        _items[newItem] = nil
    }

    func contains(item: T) -> Bool {
        if _items.indexForKey(item) != nil { return true } else { return false }
    }

    var items : [T] { get { return [T](_items.keys) } }
    var count : Int { get { return _items.count } }
}

3

实际上,您可以很容易地创建一个Set对象(与GoZoner相反,它具有内置的contains方法):

class Set<T : Equatable> {
    var items : T[] = []

    func add(item : T) {
        if !contains(items, {$0 == item}) {
            items += item
        }
    }
}

你可能甚至想声明自定义运算符:

@assignment @infix func += <T : Equatable> (inout set : Set<T>, items : T[]) -> Set<T> {
    for item in items {
        set.add(item)
    }
    return set
}

2
我不确定为什么这个被踩了,虽然它不是很高效,但对于小数据集来说绝对是可行的解决方案。 - Benjamin Gruenbaum

2

在这种情况下,关键因素是如何比较对象以及哪些类型的对象放入Set中。使用Swift字典作为Set对象的键可能会有问题,基于键类型(String、Int、Double、Bool、无值枚举或可哈希)的限制。

如果您能定义一个哈希函数,则可以使用字典。如果对象是可排序的,那么可以定义一棵树。如果对象只能通过 == 进行比较,则需要遍历Set元素以检测是否存在预先存在的对象。

// When T is only Equatable
class Set<T: Equatable> {
  var items = Array<T>()

  func hasItem (that: T) {
   // No builtin Array method of hasItem... 
   //   because comparison is undefined in builtin Array   
   for this: T in items {
     if (this == that) {
       return true
     }
   }
   return false
  }

  func insert (that: T) {
    if (!hasItem (that))
      items.append (that)
  }
}

上面是构建Swift Set的示例;该示例使用的对象仅为Equatable,这虽然是常见情况,但并不一定会导致高效的Set实现(O(N)搜索复杂度-上述是一个示例)。

1
我认为Equatable比Comparable更正确。Equatable被定义为protocol Equatable { func ==(lhs: Self, rhs: Self) -> Bool },而Comparable是protocol Comparable : Equatable { func <=(lhs: Self, rhs: Self) -> Bool func >=(lhs: Self, rhs: Self) -> Bool func >(lhs: Self, rhs: Self) -> Bool } - MarkAurelius

1
我写了一个函数来解决这个问题。
public func removeDuplicates<C: ExtensibleCollectionType where C.Generator.Element : Equatable>(aCollection: C) -> C {
    var container = C()

    for element in aCollection {
        if !contains(container, element) {
            container.append(element)
        }
    }

    return container
}

只需将包含重复元素的数组传递给此函数即可使用它。然后它将返回一个保证唯一性的数组。

如果您喜欢,还可以传递DictionaryString或符合ExtensibleCollectionType协议的任何内容。


请注意,此实现将保留每个重复项的第一个出现。 - Cœur

1

虽然对于一些元素来说,数组可能更快。Cocoa集合类通常会根据您使用的元素数量返回不同的NSArray、NSSet等子类。 - Erik Engheim
集合实现没有考虑哈希冲突,即两个对象创建相同的哈希值,尽管它们并不相同。我认为一个完整的实现必须考虑相等性以及哈希。 - nacross

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