215 lines
6.9 KiB
Swift
215 lines
6.9 KiB
Swift
/// A dictionary with guaranteed keys ordering.
|
|
///
|
|
/// var dict = OrderedDictionary<String, Int>()
|
|
/// dict.append(1, forKey: "foo")
|
|
/// dict.append(2, forKey: "bar")
|
|
///
|
|
/// dict["foo"] // 1
|
|
/// dict["bar"] // 2
|
|
/// dict["qux"] // nil
|
|
/// dict.map { $0.key } // ["foo", "bar"], in this order.
|
|
struct OrderedDictionary<Key: Hashable, Value> {
|
|
private(set) var keys: [Key]
|
|
private(set) var dictionary: [Key: Value]
|
|
|
|
var values: [Value] { keys.map { dictionary[$0]! } }
|
|
|
|
private init(keys: [Key], dictionary: [Key: Value]) {
|
|
assert(Set(keys) == Set(dictionary.keys))
|
|
self.keys = keys
|
|
self.dictionary = dictionary
|
|
}
|
|
|
|
/// Creates an empty ordered dictionary.
|
|
init() {
|
|
keys = []
|
|
dictionary = [:]
|
|
}
|
|
|
|
/// Creates an empty ordered dictionary.
|
|
init(minimumCapacity: Int) {
|
|
keys = []
|
|
keys.reserveCapacity(minimumCapacity)
|
|
dictionary = Dictionary(minimumCapacity: minimumCapacity)
|
|
}
|
|
|
|
/// Returns the value associated with key, or nil.
|
|
subscript(_ key: Key) -> Value? {
|
|
get { dictionary[key] }
|
|
set {
|
|
if let value = newValue {
|
|
updateValue(value, forKey: key)
|
|
} else {
|
|
removeValue(forKey: key)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns the value associated with key, or the default value.
|
|
subscript(_ key: Key, default defaultValue: Value) -> Value {
|
|
get { dictionary[key] ?? defaultValue }
|
|
set { self[key] = newValue }
|
|
}
|
|
|
|
/// Appends the given value for the given key.
|
|
///
|
|
/// - precondition: There is no value associated with key yet.
|
|
mutating func appendValue(_ value: Value, forKey key: Key) {
|
|
guard updateValue(value, forKey: key) == nil else {
|
|
fatalError("key is already defined")
|
|
}
|
|
}
|
|
|
|
/// Updates the value stored in the dictionary for the given key, or
|
|
/// appnds a new key-value pair if the key does not exist.
|
|
///
|
|
/// Use this method instead of key-based subscripting when you need to know
|
|
/// whether the new value supplants the value of an existing key. If the
|
|
/// value of an existing key is updated, updateValue(_:forKey:) returns the
|
|
/// original value. If the given key is not present in the dictionary, this
|
|
/// method appends the key-value pair and returns nil.
|
|
@discardableResult
|
|
mutating func updateValue(_ value: Value, forKey key: Key) -> Value? {
|
|
if let oldValue = dictionary.updateValue(value, forKey: key) {
|
|
return oldValue
|
|
}
|
|
keys.append(key)
|
|
return nil
|
|
}
|
|
|
|
/// Removes the value associated with key.
|
|
@discardableResult
|
|
mutating func removeValue(forKey key: Key) -> Value? {
|
|
guard let value = dictionary.removeValue(forKey: key) else {
|
|
return nil
|
|
}
|
|
let index = keys.firstIndex { $0 == key }!
|
|
keys.remove(at: index)
|
|
return value
|
|
}
|
|
|
|
/// Returns a new ordered dictionary containing the keys of this dictionary
|
|
/// with the values transformed by the given closure.
|
|
func mapValues<T>(_ transform: (Value) throws -> T) rethrows -> OrderedDictionary<Key, T> {
|
|
try reduce(into: .init()) { dict, pair in
|
|
let value = try transform(pair.value)
|
|
dict.appendValue(value, forKey: pair.key)
|
|
}
|
|
}
|
|
|
|
/// Returns a new ordered dictionary containing only the key-value pairs
|
|
/// that have non-nil values as the result of transformation by the
|
|
/// given closure.
|
|
func compactMapValues<T>(_ transform: (Value) throws -> T?) rethrows -> OrderedDictionary<Key, T> {
|
|
try reduce(into: .init()) { dict, pair in
|
|
if let value = try transform(pair.value) {
|
|
dict.appendValue(value, forKey: pair.key)
|
|
}
|
|
}
|
|
}
|
|
|
|
func filter(_ isIncluded: ((key: Key, value: Value)) throws -> Bool) rethrows -> OrderedDictionary<Key, Value> {
|
|
let dictionary = try self.dictionary.filter(isIncluded)
|
|
let keys = self.keys.filter(dictionary.keys.contains)
|
|
return OrderedDictionary(keys: keys, dictionary: dictionary)
|
|
}
|
|
|
|
mutating func merge<S>(
|
|
_ other: S,
|
|
uniquingKeysWith combine: (Value, Value) throws -> Value)
|
|
rethrows
|
|
where S: Sequence, S.Element == (Key, Value)
|
|
{
|
|
for (key, value) in other {
|
|
if let current = self[key] {
|
|
self[key] = try combine(current, value)
|
|
} else {
|
|
self[key] = value
|
|
}
|
|
}
|
|
}
|
|
|
|
mutating func merge<S>(
|
|
_ other: S,
|
|
uniquingKeysWith combine: (Value, Value) throws -> Value)
|
|
rethrows
|
|
where S: Sequence, S.Element == (key: Key, value: Value)
|
|
{
|
|
for (key, value) in other {
|
|
if let current = self[key] {
|
|
self[key] = try combine(current, value)
|
|
} else {
|
|
self[key] = value
|
|
}
|
|
}
|
|
}
|
|
|
|
func merging<S>(
|
|
_ other: S,
|
|
uniquingKeysWith combine: (Value, Value) throws -> Value)
|
|
rethrows -> OrderedDictionary<Key, Value>
|
|
where S: Sequence, S.Element == (Key, Value)
|
|
{
|
|
var result = self
|
|
try result.merge(other, uniquingKeysWith: combine)
|
|
return result
|
|
}
|
|
|
|
func merging<S>(
|
|
_ other: S,
|
|
uniquingKeysWith combine: (Value, Value) throws -> Value)
|
|
rethrows -> OrderedDictionary<Key, Value>
|
|
where S: Sequence, S.Element == (key: Key, value: Value)
|
|
{
|
|
var result = self
|
|
try result.merge(other, uniquingKeysWith: combine)
|
|
return result
|
|
}
|
|
}
|
|
|
|
extension OrderedDictionary: Collection {
|
|
typealias Index = Int
|
|
|
|
var startIndex: Int { 0 }
|
|
var endIndex: Int { keys.count }
|
|
|
|
func index(after i: Int) -> Int { i + 1 }
|
|
|
|
subscript(position: Int) -> (key: Key, value: Value) {
|
|
let key = keys[position]
|
|
return (key: key, value: dictionary[key]!)
|
|
}
|
|
}
|
|
|
|
extension OrderedDictionary: ExpressibleByDictionaryLiteral {
|
|
init(dictionaryLiteral elements: (Key, Value)...) {
|
|
self.keys = elements.map { $0.0 }
|
|
self.dictionary = Dictionary(uniqueKeysWithValues: elements)
|
|
}
|
|
}
|
|
|
|
extension OrderedDictionary: Equatable where Value: Equatable {
|
|
static func == (lhs: OrderedDictionary, rhs: OrderedDictionary) -> Bool {
|
|
(lhs.keys == rhs.keys) && (lhs.dictionary == rhs.dictionary)
|
|
}
|
|
}
|
|
|
|
extension OrderedDictionary: CustomStringConvertible {
|
|
var description: String {
|
|
let chunks = map { (key, value) in
|
|
"\(String(reflecting: key)): \(String(reflecting: value))"
|
|
}
|
|
if chunks.isEmpty {
|
|
return "[:]"
|
|
} else {
|
|
return "[\(chunks.joined(separator: ", "))]"
|
|
}
|
|
}
|
|
}
|
|
|
|
extension Dictionary {
|
|
init(_ orderedDictionary: OrderedDictionary<Key, Value>) {
|
|
self = orderedDictionary.dictionary
|
|
}
|
|
}
|