首页 > 解决方案 > 如何在 Swift 中实现 CountedSet (NSCountedSet)?

问题描述

创建一个受限于 Hashable 元素的通用 CountedSet 结构。计数集是唯一元素的无序集合,可能在集合中出现多次。使用私有字典作为集合成员及其计数的后备存储。

struct CountedSet<Element> {
    private(set) var elements: [Element]
    
    mutating func insert(_ element: Element) {
        elements.append(element)
    }
    
    mutating func remove() -> Element? {
        guard elements.isEmpty == false else { return nil}
        return elements.removeFirst()
        
    }
    
    subscript(_ member: Element) -> Int {
        return 0
    }
}

我不明白这里的真正目的是什么。至少对我来说,这些说明非常令人困惑。

标签: swiftgenerics

解决方案


1)使您的通用结构元素符合Hashable,这是必要的,因为字典键必须符合Hashable

struct CountedSet<Element: Hashable>

2)您使用的后备存储是有序数组,而不是字典,您需要使用空数组对其进行初始化。

private(set) var elements: [Element: Int] = [:]

3)您的下标方法需要返回计数集成员的计数,如果它为零,则返回零。

return elements[member] ?? 0

4) 您的 Insert 和 Remove 方法需要先检查支持字典中成员的计数,然后再从中添加或删除元素。

所以你的 CountedSet 应该是这样的:

struct CountedSet<Element: Hashable> {
    private(set) var elements: [Element: Int] = [:]
    mutating func insert(_ member: Element) {
        elements[member, default: 0] += 1
    }
    mutating func remove(_ member: Element) -> Element? {
        guard var count = elements[member], count > 0 else { return nil }
        count -= 1
        elements[member] = count == 0 ? nil : count
        return member
    }
    subscript(_ member: Element) -> Int {
        elements[member] ?? 0
    }
}

var countedSet = CountedSet<Int>()
countedSet.insert(3)
countedSet.insert(3)
countedSet.insert(4)
countedSet.elements  // [4: 1, 3: 2]
countedSet.remove(4)
countedSet.elements  // [3: 2]
countedSet.remove(4) // nil

对此进行扩展,您还可以使您的CountedSet符合以ExpressibleByArrayLiteral允许您CountedSet使用数组初始化并CustomStringConvertible允许您打印其元素:

extension CountedSet: ExpressibleByArrayLiteral, CustomStringConvertible {

    typealias ArrayLiteralElement = Element
    init<S: Sequence>(_ sequence: S) where S.Element == Element {
        self.elements = sequence.reduce(into: [:]) { $0[$1, default: 0] += 1 }
    }
    init(arrayLiteral elements: Element...)  { self.init(elements) }
    var description: String { .init(describing: elements) }
}

var countedSet: CountedSet = [1,2,2,3,3,3,4,4,5,5,5]
print(countedSet) // "[5: 3, 2: 2, 3: 3, 4: 2, 1: 1]\n"

推荐阅读