Skip to content

Instantly share code, notes, and snippets.

@erica
Last active January 6, 2017 03:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save erica/b6a7351f5da8f5340d6134cf2deff439 to your computer and use it in GitHub Desktop.
Save erica/b6a7351f5da8f5340d6134cf2deff439 to your computer and use it in GitHub Desktop.
import Foundation
/// A "collection" of paired items
public struct AssociativeArray<PairedItem: Hashable>: CustomStringConvertible, ExpressibleByDictionaryLiteral {
/// Constructable outside module
public init() {}
public typealias DictForm = Dictionary<PairedItem, PairedItem>
/// The true 2-way association backing dictionary
private var backingDict: DictForm = [:]
/// The user-facing version which updates synchronously
/// so there is no overhead for user facing forms.
private var userFacingDict: DictForm = [:]
/// Supports thread safety
private var accessQueue = DispatchQueue(label: "org.sadun.2WayArray", attributes: [.concurrent])
/// Allow paired access
public subscript(item1: PairedItem) -> PairedItem? {
get {
return accessQueue.sync { return backingDict[item1] }
}
set(newItem2) {
return accessQueue.sync(flags: [.barrier]) {
// No op for same value
let item2 = backingDict[item1]
guard item2 != newItem2 else { return }
switch (item2, newItem2) {
case (let item2?, let newItem2?):
// Sanitize newItem2 if paired
let newItem1 = backingDict[newItem2]
backingDict.removeValue(forKey: newItem2)
userFacingDict.removeValue(forKey: newItem2)
if newItem1 != nil {
let newItem1: PairedItem! = newItem1
backingDict.removeValue(forKey: newItem1)
userFacingDict.removeValue(forKey: newItem1)
}
// Sanitize item2
backingDict.removeValue(forKey: item2)
userFacingDict.removeValue(forKey: item2)
// Introduce new pair
backingDict[newItem2] = item1
backingDict[item1] = newItem2
userFacingDict[item1] = newItem2
case (let item2?, nil):
backingDict.removeValue(forKey: item1)
backingDict.removeValue(forKey: item2)
userFacingDict.removeValue(forKey: item1)
userFacingDict.removeValue(forKey: item2)
case (nil, let newItem2?):
backingDict[newItem2] = item1
backingDict[item1] = newItem2
userFacingDict[item1] = newItem2
default:
assertionFailure("Should never get here")
}
}
}
}
// Support dictionary literal conformance
public typealias Key = PairedItem
public typealias Value = PairedItem
/// Allows dictionary literal initialization
public init(dictionaryLiteral elements: (PairedItem, PairedItem)...) {
self.init()
for (item1, item2) in elements {
self[item1] = item2
}
}
/// Returns reduced version with duplicates removed
public var dictionaryRepresentation: [PairedItem: PairedItem] {
return userFacingDict
}
/// Provides human-facing description
public var description: String {
return "\(dictionaryRepresentation)"
}
}
/// For public consumption in most common use case
public typealias PairedStrings = AssociativeArray<String>
import Foundation
var test1 = AssociativeArray<String>()
test1["a"] = "b"
print(test1)
assert(test1["b"] == "a")
assert(test1["a"] == "b")
var test2: PairedStrings = [:]
print(test2)
var test3: PairedStrings = [
"a": "b",
"c": "d",
"e": "f",
"d": "a" // removes "a":"b" and "c":"d"
]
assert(test3.dictionaryRepresentation == ["f": "e", "a": "d"])
print(test3)
test3["b"] = "c"
assert(test3.dictionaryRepresentation == ["b": "c", "f": "e", "a": "d"])
print(test3)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment