diff --git a/Sources/HummingbirdCore/Utils/FlatDictionary.swift b/Sources/HummingbirdCore/Utils/FlatDictionary.swift index bdfb2bc3..11c6867a 100644 --- a/Sources/HummingbirdCore/Utils/FlatDictionary.swift +++ b/Sources/HummingbirdCore/Utils/FlatDictionary.swift @@ -26,76 +26,112 @@ public struct FlatDictionary: Collection, ExpressibleByDic public typealias Element = (key: Key, value: Value) public typealias Index = Array.Index + @usableFromInline + struct Storage { + @usableFromInline internal /* private */ var elements: [Element] + @usableFromInline internal /* private */ var hashKeys: [Int] + + @usableFromInline + init(elements: [Element], hashKeys: [Int]) { + self.elements = elements + self.hashKeys = hashKeys + } + } + + @usableFromInline internal /* private */ var storage: Storage? + // MARK: Collection requirements /// The position of the first element - public var startIndex: Index { self.elements.startIndex } + @inlinable + public var startIndex: Index { self.storage?.elements.startIndex ?? 0 } /// The position of the element just after the last element - public var endIndex: Index { self.elements.endIndex } + @inlinable + public var endIndex: Index { self.storage?.elements.endIndex ?? 0 } /// Access element at specific position - public subscript(_ index: Index) -> Element { return self.elements[index] } + @inlinable + public subscript(_ index: Index) -> Element { return self.storage!.elements[index] } /// Returns the index immediately after the given index - public func index(after index: Index) -> Index { self.elements.index(after: index) } + @inlinable + public func index(after index: Index) -> Index { index + 1 } /// Create a new FlatDictionary - public init() { - self.elements = [] - self.hashKeys = [] - } + @inlinable + public init() {} /// Create a new FlatDictionary initialized with a dictionary literal public init(dictionaryLiteral elements: (Key, Value)...) { - self.elements = elements.map { (key: $0.0, value: $0.1) } - self.hashKeys = elements.map { - Self.hashKey($0.0) - } + self.storage = Storage( + elements: elements.map { (key: $0.0, value: $0.1) }, + hashKeys: elements.map { + Self.hashKey($0.0) + } + ) } /// Create a new FlatDictionary from an array of key value pairs public init(_ values: [Element]) { - self.elements = values - self.hashKeys = values.map { - Self.hashKey($0.key) - } + self.storage = Storage( + elements: values, + hashKeys: values.map { + Self.hashKey($0.0) + } + ) } /// Access the value associated with a given key for reading and writing /// /// Because FlatDictionary allows for key clashes this function will /// return the first entry in the array with the associated key + @inlinable public subscript(_ key: Key) -> Value? { get { let hashKey = Self.hashKey(key) - if let index = hashKeys.firstIndex(of: hashKey) { - return self.elements[index].value + if let storage, let index = storage.hashKeys.firstIndex(of: hashKey) { + return storage.elements[index].value } else { return nil } } set { let hashKey = Self.hashKey(key) - if let index = hashKeys.firstIndex(of: hashKey) { + if var storage, let index = storage.hashKeys.firstIndex(of: hashKey) { if let newValue { - self.elements[index].value = newValue + storage.elements[index].value = newValue } else { - self.elements.remove(at: index) - self.hashKeys.remove(at: index) + storage.elements.remove(at: index) + storage.hashKeys.remove(at: index) } + + self.storage = storage } else if let newValue { - self.elements.append((key: key, value: newValue)) - self.hashKeys.append(hashKey) + if var storage { + storage.elements.append((key: key, value: newValue)) + storage.hashKeys.append(hashKey) + self.storage = storage + } else { + self.storage = Storage( + elements: [(key: key, value: newValue)], + hashKeys: [hashKey] + ) + } } } } /// Return all the values, associated with a given key + @inlinable public subscript(values key: Key) -> [Value] { + guard let storage else { + return [] + } + var values: [Value] = [] let hashKey = Self.hashKey(key) - for hashIndex in 0..: Collection, ExpressibleByDic /// Return if dictionary has this value /// - Parameter key: + @inlinable public func has(_ key: Key) -> Bool { + guard let storage else { + return false + } + let hashKey = Self.hashKey(key) - return self.hashKeys.firstIndex(of: hashKey) != nil + return storage.hashKeys.firstIndex(of: hashKey) != nil } /// Append a new key value pair to the list of key value pairs + @inlinable public mutating func append(key: Key, value: Value) { let hashKey = Self.hashKey(key) - self.elements.append((key: key, value: value)) - self.hashKeys.append(hashKey) + + if var storage { + storage.elements.append((key: key, value: value)) + storage.hashKeys.append(hashKey) + self.storage = storage + } else { + self.storage = Storage( + elements: [(key: key, value: value)], + hashKeys: [hashKey] + ) + } } - private static func hashKey(_ key: Key) -> Int { + @usableFromInline + internal /* private */ static func hashKey(_ key: Key) -> Int { var hasher = Hasher() hasher.combine(key) return hasher.finalize() } - - private var elements: [Element] - private var hashKeys: [Int] } // FlatDictionary is Sendable when Key and Value are Sendable +extension FlatDictionary.Storage: Sendable where Key: Sendable, Value: Sendable {} extension FlatDictionary: Sendable where Key: Sendable, Value: Sendable {}