// // UnorderedSparseSet.swift // FirebladeECS // // Created by Christian Treffs on 30.10.17. // /// An unordered sparse set. /// /// - `Element`: the element (instance) to store. /// - `Key`: the unique, hashable datastructure to use as a key to retrieve /// an element from the sparse set. /// /// See for a reference implementation. public struct UnorderedSparseSet { // swiftlint:disable nesting @usableFromInline final class Storage { /// An index into the dense store. @usableFromInline typealias DenseIndex = Int /// A sparse store holding indices into the dense mapped to key. @usableFromInline typealias SparseStore = [Key: DenseIndex] /// A dense store holding all the entries. @usableFromInline typealias DenseStore = ContiguousArray @usableFromInline struct Entry { @usableFromInline let key: Key @usableFromInline let element: Element @usableFromInline init(key: Key, element: Element) { self.key = key self.element = element } } @usableFromInline var dense: DenseStore @usableFromInline var sparse: SparseStore @usableFromInline init(sparse: SparseStore, dense: DenseStore) { self.sparse = sparse self.dense = dense } @usableFromInline convenience init() { self.init(sparse: [:], dense: []) } @usableFromInline var count: Int { dense.count } @usableFromInline var isEmpty: Bool { dense.isEmpty } @inlinable var first: Element? { dense.first?.element } @inlinable func findIndex(at key: Key) -> Int? { guard let denseIndex = sparse[key], denseIndex < count else { return nil } return denseIndex } @inlinable func findElement(at key: Key) -> Element? { guard let denseIndex = findIndex(at: key) else { return nil } let entry = dense[denseIndex] assert(entry.key == key, "entry.key and findIndex(at: key) must be equal!") return entry.element } @inlinable func insert(_ element: Element, at key: Key) -> Bool { if let denseIndex = findIndex(at: key) { dense[denseIndex] = Entry(key: key, element: element) return false } let nIndex = dense.count dense.append(Entry(key: key, element: element)) sparse.updateValue(nIndex, forKey: key) return true } @inlinable func remove(at key: Key) -> Entry? { guard let denseIndex = findIndex(at: key) else { return nil } let removed = swapRemove(at: denseIndex) if !dense.isEmpty, denseIndex < dense.count { let swappedElement = dense[denseIndex] sparse[swappedElement.key] = denseIndex } sparse[key] = nil return removed } /// Removes an element from the set and returns it in O(1). /// The removed element is replaced with the last element of the set. /// /// - Parameter denseIndex: the dense index /// - Returns: the element entry @inlinable func swapRemove(at denseIndex: Int) -> Entry { dense.swapAt(denseIndex, dense.count - 1) return dense.removeLast() } @inlinable func removeAll(keepingCapacity: Bool = false) { sparse.removeAll(keepingCapacity: keepingCapacity) dense.removeAll(keepingCapacity: keepingCapacity) } @inlinable func makeIterator() -> IndexingIterator> { dense.makeIterator() } } /// Creates a new sparse set. public init() { self.init(storage: Storage()) } @usableFromInline init(storage: Storage) { self.storage = storage } @usableFromInline let storage: Storage /// The size of the set. public var count: Int { storage.count } /// A Boolean value that indicates whether the set is empty. public var isEmpty: Bool { storage.isEmpty } /// Returns a Boolean value that indicates whether the key is included in the set. /// - Parameter key: The key to inspect. @inlinable public func contains(_ key: Key) -> Bool { storage.findIndex(at: key) != nil } /// Inset an element for a given key into the set in O(1). /// /// Elements at previously set keys will be replaced. /// /// - Parameters: /// - element: The element. /// - key: The key. /// - Returns: `true` if new, `false` if replaced. @discardableResult public func insert(_ element: Element, at key: Key) -> Bool { storage.insert(element, at: key) } /// Get the element for the given key in O(1). /// /// - Parameter key: The key. /// - Returns: the element or `nil` if the key wasn't found. @inlinable public func get(at key: Key) -> Element? { storage.findElement(at: key) } /// Unsafely gets the element for the given key, /// - Parameter key: The key. /// - Returns: The element. @inlinable public func get(unsafeAt key: Key) -> Element { storage.findElement(at: key).unsafelyUnwrapped } /// Removes the element entry for given key in O(1). /// /// - Parameter key: the key /// - Returns: removed value or nil if key not found. @discardableResult public func remove(at key: Key) -> Element? { storage.remove(at: key)?.element } /// Removes all keys and elements from the set. /// - Parameter keepingCapacity: A Boolean value that indicates whether the set should maintain it's capacity. @inlinable public func removeAll(keepingCapacity: Bool = false) { storage.removeAll(keepingCapacity: keepingCapacity) } /// The first element of the set. @inlinable public var first: Element? { storage.first } } extension UnorderedSparseSet where Key == Int { /// Retrieve or set an element using the key. @inlinable public subscript(key: Key) -> Element { get { get(unsafeAt: key) } nonmutating set(newValue) { insert(newValue, at: key) } } } // MARK: - Sequence extension UnorderedSparseSet: Sequence { public func makeIterator() -> ElementIterator { ElementIterator(self) } // MARK: - UnorderedSparseSetIterator public struct ElementIterator: IteratorProtocol { var iterator: IndexingIterator> public init(_ sparseSet: UnorderedSparseSet) { iterator = sparseSet.storage.makeIterator() } public mutating func next() -> Element? { iterator.next()?.element } } } extension UnorderedSparseSet.ElementIterator: LazySequenceProtocol {} extension UnorderedSparseSet.ElementIterator: Sequence {} // MARK: - Equatable extension UnorderedSparseSet.Storage.Entry: Equatable where Element: Equatable {} extension UnorderedSparseSet.Storage: Equatable where Element: Equatable { @usableFromInline static func == (lhs: UnorderedSparseSet.Storage, rhs: UnorderedSparseSet.Storage) -> Bool { lhs.dense == rhs.dense && lhs.sparse == rhs.sparse } } extension UnorderedSparseSet: Equatable where Element: Equatable { public static func == (lhs: UnorderedSparseSet, rhs: UnorderedSparseSet) -> Bool { lhs.storage == rhs.storage } } // MARK: - Codable extension UnorderedSparseSet.Storage.Entry: Codable where Element: Codable {} extension UnorderedSparseSet.Storage: Codable where Element: Codable {} extension UnorderedSparseSet: Codable where Element: Codable {}