Skip to content

Commit

Permalink
Merge pull request #126 from ReactiveCocoa/bag-perf
Browse files Browse the repository at this point in the history
Optimize `Bag`.
  • Loading branch information
andersio authored Nov 27, 2016
2 parents 0fcbdc6 + 439f535 commit dba767d
Showing 1 changed file with 39 additions and 42 deletions.
81 changes: 39 additions & 42 deletions Sources/Bag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,25 @@

/// A uniquely identifying token for removing a value that was inserted into a
/// Bag.
public final class RemovalToken {
fileprivate var identifier: UInt?

fileprivate init(identifier: UInt) {
self.identifier = identifier
}
}
public final class RemovalToken {}

/// An unordered, non-unique collection of values of type `Element`.
public struct Bag<Element> {
fileprivate var elements: [BagElement<Element>] = []
private var currentIdentifier: UInt = 0
fileprivate var elements: ContiguousArray<BagElement<Element>> = []

public init() {
}
public init() {}

/// Insert the given value into `self`, and return a token that can
/// later be passed to `removeValueForToken()`.
/// later be passed to `remove(using:)`.
///
/// - parameters:
/// - value: A value that will be inserted.
@discardableResult
public mutating func insert(_ value: Element) -> RemovalToken {
let (nextIdentifier, overflow) = UInt.addWithOverflow(currentIdentifier, 1)
if overflow {
reindex()
}

let token = RemovalToken(identifier: currentIdentifier)
let element = BagElement(value: value, identifier: currentIdentifier, token: token)
let token = RemovalToken()
let element = BagElement(value: value, token: token)

elements.append(element)
currentIdentifier = nextIdentifier

return token
}

Expand All @@ -52,29 +37,15 @@ public struct Bag<Element> {
/// - parameters:
/// - token: A token returned from a call to `insert()`.
public mutating func remove(using token: RemovalToken) {
if let identifier = token.identifier {
// Removal is more likely for recent objects than old ones.
for i in elements.indices.reversed() {
if elements[i].identifier == identifier {
elements.remove(at: i)
token.identifier = nil
break
}
let tokenIdentifier = ObjectIdentifier(token)
// Removal is more likely for recent objects than old ones.
for i in elements.indices.reversed() {
if ObjectIdentifier(elements[i].token) == tokenIdentifier {
elements.remove(at: i)
break
}
}
}

/// In the event of an identifier overflow (highly, highly unlikely), reset
/// all current identifiers to reclaim a contiguous set of available
/// identifiers for the future.
private mutating func reindex() {
for i in elements.indices {
currentIdentifier = UInt(i)

elements[i].identifier = currentIdentifier
elements[i].token.identifier = currentIdentifier
}
}
}

extension Bag: Collection {
Expand All @@ -95,11 +66,14 @@ extension Bag: Collection {
public func index(after i: Index) -> Index {
return i + 1
}

public func makeIterator() -> BagIterator<Element> {
return BagIterator(elements)
}
}

private struct BagElement<Value> {
let value: Value
var identifier: UInt
let token: RemovalToken
}

Expand All @@ -108,3 +82,26 @@ extension BagElement: CustomStringConvertible {
return "BagElement(\(value))"
}
}

public struct BagIterator<Element>: IteratorProtocol {
private let base: ContiguousArray<BagElement<Element>>
private var nextIndex: Int
private let endIndex: Int

fileprivate init(_ base: ContiguousArray<BagElement<Element>>) {
self.base = base
nextIndex = base.startIndex
endIndex = base.endIndex
}

public mutating func next() -> Element? {
let currentIndex = nextIndex

if currentIndex < endIndex {
nextIndex = currentIndex + 1
return base[currentIndex].value
}

return nil
}
}

0 comments on commit dba767d

Please sign in to comment.