CircularBuffer: shrink indices to 1 word on 64-bit archs
Motivation: CircularBuffer and its indices should be small. Also the implementation of methods like remove/popFirst/Last was suboptimal. Modifications: - shrink CircularBuffer and its indices - improve the conformances to the various collection related protocols - make CircularBuffer self-slicing - remove Strideable conformance from the indices Result: nicer CircularBuffer
This commit is contained in:
parent
5409acdfb6
commit
35ed9ff754
|
@ -95,6 +95,7 @@ extension ByteBuffer {
|
|||
|
||||
extension FixedWidthInteger {
|
||||
/// Returns the next power of two.
|
||||
@inlinable
|
||||
func nextPowerOf2() -> Self {
|
||||
guard self != 0 else {
|
||||
return 1
|
||||
|
|
|
@ -12,74 +12,65 @@
|
|||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/// AppendableCollection is a protocol partway between Collection and
|
||||
/// RangeReplaceableCollection. It defines the append method that is present
|
||||
/// on RangeReplaceableCollection, which makes all RangeReplaceableCollections
|
||||
/// trivially able to implement this protocol.
|
||||
protocol AppendableCollection: Collection {
|
||||
mutating func append(_ newElement: Self.Iterator.Element)
|
||||
}
|
||||
|
||||
/// An automatically expanding ring buffer implementation backed by a `ContiguousArray`. Even though this implementation
|
||||
/// will automatically expand if more elements than `initialCapacity` are stored, it's advantageous to prevent
|
||||
/// expansions from happening frequently. Expansions will always force an allocation and a copy to happen.
|
||||
public struct CircularBuffer<Element>: CustomStringConvertible, AppendableCollection {
|
||||
public typealias RangeType<Bound> = Range<Bound> where Bound: Strideable, Bound.Stride: SignedInteger
|
||||
private var buffer: ContiguousArray<Element?>
|
||||
public struct CircularBuffer<Element>: CustomStringConvertible {
|
||||
@usableFromInline
|
||||
internal var _buffer: ContiguousArray<Element?>
|
||||
|
||||
/// The index into the buffer of the first item
|
||||
private(set) /* private but tests */ internal var headIdx: Index
|
||||
@usableFromInline
|
||||
internal var headBackingIndex: Int = 0
|
||||
|
||||
/// The index into the buffer of the next free slot
|
||||
private(set) /* private but tests */ internal var tailIdx: Index
|
||||
@usableFromInline
|
||||
internal var tailBackingIndex: Int = 0
|
||||
|
||||
public struct Index: Strideable, Comparable {
|
||||
@usableFromInline var backingIndex: ContiguousArray<Element?>.Index
|
||||
@usableFromInline let backingCount: Int
|
||||
@inlinable
|
||||
internal var mask: Int {
|
||||
return self._buffer.count - 1
|
||||
}
|
||||
|
||||
@inlinable
|
||||
internal mutating func advanceHeadIdx(by: Int) {
|
||||
self.headBackingIndex = indexAdvanced(index: self.headBackingIndex, by: by)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
internal mutating func advanceTailIdx(by: Int) {
|
||||
self.tailBackingIndex = indexAdvanced(index: self.tailBackingIndex, by: by)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
internal func indexBeforeHeadIdx() -> Int {
|
||||
return self.indexAdvanced(index: self.headBackingIndex, by: -1)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
internal func indexBeforeTailIdx() -> Int {
|
||||
return self.indexAdvanced(index: self.tailBackingIndex, by: -1)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
internal func indexAdvanced(index: Int, by: Int) -> Int {
|
||||
return (index + by) & self.mask
|
||||
}
|
||||
|
||||
public struct Index: Comparable {
|
||||
@usableFromInline var _backingIndex: UInt32
|
||||
@usableFromInline var isIndexGEQHeadIndex: Bool
|
||||
@inlinable var mask: Int { return self.backingCount - 1 }
|
||||
|
||||
@inlinable
|
||||
internal init(backingIndex: ContiguousArray<Element?>.Index, backingIndexOfHead: ContiguousArray<Element?>.Index, backingCount: Int) {
|
||||
internal var backingIndex: Int {
|
||||
return Int(self._backingIndex)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
internal init(backingIndex: Int, backingIndexOfHead: Int) {
|
||||
self.isIndexGEQHeadIndex = backingIndex >= backingIndexOfHead
|
||||
self.backingCount = backingCount
|
||||
self.backingIndex = backingIndex
|
||||
}
|
||||
|
||||
private init(backingIndex: ContiguousArray<Element?>.Index, greater: Bool, backingCount: Int) {
|
||||
self.backingIndex = backingIndex
|
||||
self.isIndexGEQHeadIndex = greater
|
||||
self.backingCount = backingCount
|
||||
self._backingIndex = UInt32(backingIndex)
|
||||
}
|
||||
|
||||
@inlinable
|
||||
public func distance(to other: Index) -> Int {
|
||||
if self.isIndexGEQHeadIndex && other.isIndexGEQHeadIndex {
|
||||
return self.backingIndex.distance(to: other.backingIndex)
|
||||
} else if self.isIndexGEQHeadIndex && !other.isIndexGEQHeadIndex {
|
||||
return self.backingIndex.distance(to: backingCount) + other.backingIndex
|
||||
} else if !self.isIndexGEQHeadIndex && other.isIndexGEQHeadIndex {
|
||||
return self.backingIndex.distance(to: 0) + backingCount.distance(to: other.backingIndex)
|
||||
} else {
|
||||
return self.backingIndex.distance(to: other.backingIndex)
|
||||
}
|
||||
}
|
||||
|
||||
public func advanced(by n: Int) -> Index {
|
||||
var index = Index(backingIndex: 0, greater: self.isIndexGEQHeadIndex, backingCount: self.backingCount)
|
||||
if n >= 0 {
|
||||
if backingIndex + n >= self.backingCount {
|
||||
index.isIndexGEQHeadIndex = !self.isIndexGEQHeadIndex
|
||||
}
|
||||
} else {
|
||||
if backingIndex + n < 0 {
|
||||
index.isIndexGEQHeadIndex = !self.isIndexGEQHeadIndex
|
||||
}
|
||||
}
|
||||
index.backingIndex = (self.backingIndex + n) & (self.mask)
|
||||
return index
|
||||
}
|
||||
|
||||
public static func < (lhs: Index, rhs: Index) -> Bool {
|
||||
if lhs.isIndexGEQHeadIndex && rhs.isIndexGEQHeadIndex {
|
||||
return lhs.backingIndex < rhs.backingIndex
|
||||
|
@ -94,18 +85,169 @@ public struct CircularBuffer<Element>: CustomStringConvertible, AppendableCollec
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: Collection/MutableCollection implementation
|
||||
extension CircularBuffer: Collection, MutableCollection {
|
||||
public typealias RangeType<Bound> = Range<Bound> where Bound: Strideable, Bound.Stride: SignedInteger
|
||||
public typealias SubSequence = CircularBuffer<Element>
|
||||
|
||||
/// Returns the position immediately after the given index.
|
||||
///
|
||||
/// The successor of an index must be well defined. For an index `i` into a
|
||||
/// collection `c`, calling `c.index(after: i)` returns the same index every
|
||||
/// time.
|
||||
///
|
||||
/// - Parameter i: A valid index of the collection. `i` must be less than
|
||||
/// `endIndex`.
|
||||
/// - Returns: The index value immediately after `i`.
|
||||
@inlinable
|
||||
public func index(after: Index) -> Index {
|
||||
return self.index(after, offsetBy: 1)
|
||||
}
|
||||
|
||||
/// Returns the index before `index`.
|
||||
@inlinable
|
||||
public func index(before: Index) -> Index {
|
||||
return self.index(before, offsetBy: -1)
|
||||
}
|
||||
|
||||
/// Accesses the element at the specified index.
|
||||
///
|
||||
/// You can subscript `CircularBuffer` with any valid index other than the
|
||||
/// `CircularBuffer`'s end index. The end index refers to the position one
|
||||
/// past the last element of a collection, so it doesn't correspond with an
|
||||
/// element.
|
||||
///
|
||||
/// - Parameter position: The position of the element to access. `position`
|
||||
/// must be a valid index of the collection that is not equal to the
|
||||
/// `endIndex` property.
|
||||
///
|
||||
/// - Complexity: O(1)
|
||||
@inlinable
|
||||
public subscript(position: Index) -> Element {
|
||||
get {
|
||||
return self._buffer[position.backingIndex]!
|
||||
}
|
||||
set {
|
||||
self._buffer[position.backingIndex] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// The position of the first element in a nonempty `CircularBuffer`.
|
||||
///
|
||||
/// If the `CircularBuffer` is empty, `startIndex` is equal to `endIndex`.
|
||||
@inlinable
|
||||
public var startIndex: Index {
|
||||
return .init(backingIndex: self.headBackingIndex, backingIndexOfHead: self.headBackingIndex)
|
||||
}
|
||||
|
||||
/// The `CircularBuffer`'s "past the end" position---that is, the position one
|
||||
/// greater than the last valid subscript argument.
|
||||
///
|
||||
/// When you need a range that includes the last element of a collection, use
|
||||
/// the half-open range operator (`..<`) with `endIndex`. The `..<` operator
|
||||
/// creates a range that doesn't include the upper bound, so it's always
|
||||
/// safe to use with `endIndex`.
|
||||
///
|
||||
/// If the `CircularBuffer` is empty, `endIndex` is equal to `startIndex`.
|
||||
@inlinable
|
||||
public var endIndex: Index {
|
||||
return .init(backingIndex: self.tailBackingIndex, backingIndexOfHead: self.headBackingIndex)
|
||||
}
|
||||
|
||||
/// Returns the distance between two indices.
|
||||
///
|
||||
/// Unless the collection conforms to the `BidirectionalCollection` protocol,
|
||||
/// `start` must be less than or equal to `end`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - start: A valid index of the collection.
|
||||
/// - end: Another valid index of the collection. If `end` is equal to
|
||||
/// `start`, the result is zero.
|
||||
/// - Returns: The distance between `start` and `end`. The result can be
|
||||
/// negative only if the collection conforms to the
|
||||
/// `BidirectionalCollection` protocol.
|
||||
///
|
||||
/// - Complexity: O(1) if the collection conforms to
|
||||
/// `RandomAccessCollection`; otherwise, O(*k*), where *k* is the
|
||||
/// resulting distance.
|
||||
@inlinable
|
||||
public func distance(from start: CircularBuffer<Element>.Index, to end: CircularBuffer<Element>.Index) -> Int {
|
||||
let backingCount = self._buffer.count
|
||||
|
||||
switch (start.isIndexGEQHeadIndex, end.isIndexGEQHeadIndex) {
|
||||
case (true, true):
|
||||
return end.backingIndex - start.backingIndex
|
||||
case (true, false):
|
||||
return backingCount - (start.backingIndex - end.backingIndex)
|
||||
case (false, true):
|
||||
return -(backingCount - (end.backingIndex - start.backingIndex))
|
||||
case (false, false):
|
||||
return end.backingIndex - start.backingIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: RandomAccessCollection implementation
|
||||
extension CircularBuffer: RandomAccessCollection {
|
||||
/// Returns the index offset by `distance` from `index`.
|
||||
@inlinable
|
||||
public func index(_ i: Index, offsetBy distance: Int) -> Index {
|
||||
return .init(backingIndex: (i.backingIndex + distance) & self.mask,
|
||||
backingIndexOfHead: self.headBackingIndex)
|
||||
}
|
||||
|
||||
/// Returns an index that is the specified distance from the given index.
|
||||
///
|
||||
/// The following example obtains an index advanced four positions from a
|
||||
/// string's starting index and then prints the character at that position.
|
||||
///
|
||||
/// let s = "Swift"
|
||||
/// let i = s.index(s.startIndex, offsetBy: 4)
|
||||
/// print(s[i])
|
||||
/// // Prints "t"
|
||||
///
|
||||
/// The value passed as `distance` must not offset `i` beyond the bounds of
|
||||
/// the collection.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - i: A valid index of the collection.
|
||||
/// - distance: The distance to offset `i`. `distance` must not be negative
|
||||
/// unless the collection conforms to the `BidirectionalCollection`
|
||||
/// protocol.
|
||||
/// - Returns: An index offset by `distance` from the index `i`. If
|
||||
/// `distance` is positive, this is the same value as the result of
|
||||
/// `distance` calls to `index(after:)`. If `distance` is negative, this
|
||||
/// is the same value as the result of `abs(distance)` calls to
|
||||
/// `index(before:)`.
|
||||
///
|
||||
/// - Complexity: O(1) if the collection conforms to
|
||||
/// `RandomAccessCollection`; otherwise, O(*k*), where *k* is the absolute
|
||||
/// value of `distance`.
|
||||
@inlinable
|
||||
public subscript(bounds: Range<Index>) -> SubSequence {
|
||||
precondition(self.distance(from: self.startIndex, to: bounds.lowerBound) >= 0)
|
||||
precondition(self.distance(from: bounds.upperBound, to: self.endIndex) >= 0)
|
||||
|
||||
var newRing = self
|
||||
newRing.headBackingIndex = bounds.lowerBound.backingIndex
|
||||
newRing.tailBackingIndex = bounds.upperBound.backingIndex
|
||||
return newRing
|
||||
}
|
||||
}
|
||||
|
||||
extension CircularBuffer {
|
||||
|
||||
/// Allocates a buffer that can hold up to `initialCapacity` elements and initialise an empty ring backed by
|
||||
/// the buffer. When the ring grows to more than `initialCapacity` elements the buffer will be expanded.
|
||||
@inlinable
|
||||
public init(initialCapacity: Int) {
|
||||
let capacity = Int(UInt32(initialCapacity).nextPowerOf2())
|
||||
self.buffer = ContiguousArray<Element?>(repeating: nil, count: capacity)
|
||||
self.headIdx = Index(backingIndex: 0, backingIndexOfHead: 0, backingCount: capacity)
|
||||
self.tailIdx = Index(backingIndex: 0, backingIndexOfHead: 0, backingCount: capacity)
|
||||
assert(self.buffer.count == capacity)
|
||||
self._buffer = ContiguousArray<Element?>(repeating: nil, count: capacity)
|
||||
assert(self._buffer.count == capacity)
|
||||
}
|
||||
|
||||
/// Allocates an empty buffer.
|
||||
@inlinable
|
||||
public init() {
|
||||
self.init(initialCapacity: 16)
|
||||
}
|
||||
|
@ -113,55 +255,57 @@ extension CircularBuffer {
|
|||
/// Append an element to the end of the ring buffer.
|
||||
///
|
||||
/// Amortized *O(1)*
|
||||
@inlinable
|
||||
public mutating func append(_ value: Element) {
|
||||
self.buffer[self.tailIdx.backingIndex] = value
|
||||
self.tailIdx = self.index(after: self.tailIdx)
|
||||
self.tailIdx.isIndexGEQHeadIndex = self.tailIdx.backingIndex >= self.headIdx.backingIndex
|
||||
self._buffer[self.tailBackingIndex] = value
|
||||
self.advanceTailIdx(by: 1)
|
||||
|
||||
if self.headIdx == self.tailIdx {
|
||||
if self.headBackingIndex == self.tailBackingIndex {
|
||||
// No more room left for another append so grow the buffer now.
|
||||
self.doubleCapacity()
|
||||
self._doubleCapacity()
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepend an element to the front of the ring buffer.
|
||||
///
|
||||
/// Amortized *O(1)*
|
||||
@inlinable
|
||||
public mutating func prepend(_ value: Element) {
|
||||
let idx = self.index(before: self.headIdx)
|
||||
self.buffer[idx.backingIndex] = value
|
||||
self.headIdx.backingIndex = idx.backingIndex
|
||||
self.headIdx.isIndexGEQHeadIndex = true
|
||||
self.tailIdx.isIndexGEQHeadIndex = self.tailIdx.backingIndex >= self.headIdx.backingIndex
|
||||
let idx = self.indexBeforeHeadIdx()
|
||||
self._buffer[idx] = value
|
||||
self.advanceHeadIdx(by: -1)
|
||||
|
||||
if self.headIdx == self.tailIdx {
|
||||
if self.headBackingIndex == self.tailBackingIndex {
|
||||
// No more room left for another append so grow the buffer now.
|
||||
self.doubleCapacity()
|
||||
self._doubleCapacity()
|
||||
}
|
||||
}
|
||||
|
||||
/// Double the capacity of the buffer and adjust the headIdx and tailIdx.
|
||||
private mutating func doubleCapacity() {
|
||||
@inlinable
|
||||
internal mutating func _doubleCapacity() {
|
||||
var newBacking: ContiguousArray<Element?> = []
|
||||
let newCapacity = self.buffer.count << 1 // Double the storage.
|
||||
precondition(newCapacity > 0, "Can't double capacity of \(self.buffer.count)")
|
||||
let newCapacity = self._buffer.count << 1 // Double the storage.
|
||||
precondition(newCapacity > 0, "Can't double capacity of \(self._buffer.count)")
|
||||
assert(newCapacity % 2 == 0)
|
||||
|
||||
newBacking.reserveCapacity(newCapacity)
|
||||
newBacking.append(contentsOf: self.buffer[self.headIdx.backingIndex..<self.buffer.count])
|
||||
if self.headIdx.backingIndex > 0 {
|
||||
newBacking.append(contentsOf: self.buffer[0..<self.headIdx.backingIndex])
|
||||
newBacking.append(contentsOf: self._buffer[self.headBackingIndex..<self._buffer.count])
|
||||
if self.headBackingIndex > 0 {
|
||||
newBacking.append(contentsOf: self._buffer[0..<self.headBackingIndex])
|
||||
}
|
||||
let repeatitionCount = newCapacity - newBacking.count
|
||||
newBacking.append(contentsOf: repeatElement(nil, count: repeatitionCount))
|
||||
self.headIdx = Index(backingIndex: 0, backingIndexOfHead: 0, backingCount: newBacking.count)
|
||||
self.tailIdx = Index(backingIndex: newBacking.count - repeatitionCount, backingIndexOfHead: self.headIdx.backingIndex, backingCount: newBacking.count)
|
||||
self.buffer = newBacking
|
||||
self.headBackingIndex = 0
|
||||
self.tailBackingIndex = newBacking.count - repeatitionCount
|
||||
self._buffer = newBacking
|
||||
assert(self.verifyInvariants())
|
||||
}
|
||||
|
||||
/// Return element `offset` from first element.
|
||||
///
|
||||
/// *O(1)*
|
||||
@inlinable
|
||||
public subscript(offset offset: Int) -> Element {
|
||||
get {
|
||||
return self[self.index(self.startIndex, offsetBy: offset)]
|
||||
|
@ -170,123 +314,216 @@ extension CircularBuffer {
|
|||
self[self.index(self.startIndex, offsetBy: offset)] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Collection implementation
|
||||
/// Return element `index` of the ring.
|
||||
///
|
||||
/// *O(1)*
|
||||
public subscript(index: Index) -> Element {
|
||||
get {
|
||||
return self.buffer[index.backingIndex]!
|
||||
}
|
||||
set {
|
||||
self.buffer[index.backingIndex] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// Return all valid indices of the ring.
|
||||
public var indices: RangeType<Index> {
|
||||
return self.startIndex ..< self.endIndex
|
||||
}
|
||||
|
||||
/// Returns whether the ring is empty.
|
||||
@inlinable
|
||||
public var isEmpty: Bool {
|
||||
return self.headIdx == self.tailIdx
|
||||
return self.headBackingIndex == self.tailBackingIndex
|
||||
}
|
||||
|
||||
/// Returns the number of element in the ring.
|
||||
@inlinable
|
||||
public var count: Int {
|
||||
return self.headIdx.distance(to: self.tailIdx)
|
||||
if self.tailBackingIndex >= self.headBackingIndex {
|
||||
return self.tailBackingIndex - self.headBackingIndex
|
||||
} else {
|
||||
return self._buffer.count - (self.headBackingIndex - self.tailBackingIndex)
|
||||
}
|
||||
}
|
||||
|
||||
/// The total number of elements that the ring can contain without allocating new storage.
|
||||
@inlinable
|
||||
public var capacity: Int {
|
||||
return self.buffer.count
|
||||
return self._buffer.count
|
||||
}
|
||||
|
||||
/// Returns the index of the first element of the ring.
|
||||
public var startIndex: Index {
|
||||
return self.headIdx
|
||||
}
|
||||
|
||||
/// Returns the ring's "past the end" position -- that is, the position one greater than the last valid subscript argument.
|
||||
public var endIndex: Index {
|
||||
return self.tailIdx
|
||||
}
|
||||
|
||||
/// Returns the next index after `index`.
|
||||
public func index(after: Index) -> Index {
|
||||
return after.advanced(by: 1)
|
||||
}
|
||||
|
||||
/// Returns the index before `index`.
|
||||
public func index(before: Index) -> Index {
|
||||
return before.advanced(by: -1)
|
||||
}
|
||||
|
||||
/// Returns the index offset by `distance` from `index`.
|
||||
public func index(_ i: Index, offsetBy distance: Int) -> Index {
|
||||
return i.advanced(by: distance)
|
||||
}
|
||||
|
||||
/// Removes all members from the circular buffer whist keeping the capacity.
|
||||
@inlinable
|
||||
public mutating func removeAll(keepingCapacity: Bool = false) {
|
||||
if keepingCapacity {
|
||||
for index in self.startIndex..<self.endIndex {
|
||||
self.buffer[index.backingIndex] = nil
|
||||
}
|
||||
self.removeFirst(self.count)
|
||||
} else {
|
||||
self.buffer.removeAll(keepingCapacity: false)
|
||||
self.buffer.append(nil)
|
||||
self._buffer.removeAll(keepingCapacity: false)
|
||||
self._buffer.append(nil)
|
||||
}
|
||||
self.headIdx = Index(backingIndex: 0, backingIndexOfHead: 0, backingCount: self.buffer.count)
|
||||
self.tailIdx = Index(backingIndex: 0, backingIndexOfHead: 0, backingCount: self.buffer.count)
|
||||
assert(self.buffer.allSatisfy { $0 == nil})
|
||||
self.headBackingIndex = 0
|
||||
self.tailBackingIndex = 0
|
||||
assert(self.verifyInvariants())
|
||||
}
|
||||
|
||||
// MARK: CustomStringConvertible implementation
|
||||
/// Returns a human readable description of the ring.
|
||||
public var description: String {
|
||||
var desc = "[ "
|
||||
for el in self.buffer.enumerated() {
|
||||
if el.0 == self.headIdx.backingIndex {
|
||||
for el in self._buffer.enumerated() {
|
||||
if el.0 == self.headBackingIndex {
|
||||
desc += "<"
|
||||
} else if el.0 == self.tailIdx.backingIndex {
|
||||
} else if el.0 == self.tailBackingIndex {
|
||||
desc += ">"
|
||||
}
|
||||
desc += el.1.map { "\($0) " } ?? "_ "
|
||||
}
|
||||
desc += "]"
|
||||
desc += " (bufferCapacity: \(self.buffer.count), ringLength: \(self.count))"
|
||||
desc += " (bufferCapacity: \(self._buffer.count), ringLength: \(self.count))"
|
||||
return desc
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - BidirectionalCollection, RandomAccessCollection, RangeReplaceableCollection
|
||||
extension CircularBuffer: BidirectionalCollection, RandomAccessCollection, RangeReplaceableCollection {
|
||||
/// Replaces the specified subrange of elements with the given collection.
|
||||
// MARK: - RangeReplaceableCollection
|
||||
extension CircularBuffer: RangeReplaceableCollection {
|
||||
/// Removes and returns the first element of the `CircularBuffer`.
|
||||
///
|
||||
/// - Parameter subrange:
|
||||
/// The subrange of the collection to replace. The bounds of the range must be valid indices of the collection.
|
||||
/// Calling this method may invalidate all saved indices of this
|
||||
/// `CircularBuffer`. Do not rely on a previously stored index value after
|
||||
/// altering a `CircularBuffer` with any operation that can change its length.
|
||||
///
|
||||
/// - Parameter newElements:
|
||||
/// The new elements to add to the collection.
|
||||
/// - Returns: The first element of the `CircularBuffer` if the `CircularBuffer` is not
|
||||
/// empty; otherwise, `nil`.
|
||||
///
|
||||
/// - Complexity: O(1)
|
||||
@inlinable
|
||||
public mutating func popFirst() -> Element? {
|
||||
if count > 0 {
|
||||
return self.removeFirst()
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes and returns the last element of the `CircularBuffer`.
|
||||
///
|
||||
/// Calling this method may invalidate all saved indices of this
|
||||
/// `CircularBuffer`. Do not rely on a previously stored index value after
|
||||
/// altering a `CircularBuffer` with any operation that can change its length.
|
||||
///
|
||||
/// - Returns: The last element of the `CircularBuffer` if the `CircularBuffer` is not
|
||||
/// empty; otherwise, `nil`.
|
||||
///
|
||||
/// - Complexity: O(1)
|
||||
@inlinable
|
||||
public mutating func popLast() -> Element? {
|
||||
if count > 0 {
|
||||
return self.removeLast()
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the specified number of elements from the end of the
|
||||
/// `CircularBuffer`.
|
||||
///
|
||||
/// Attempting to remove more elements than exist in the `CircularBuffer`
|
||||
/// triggers a runtime error.
|
||||
///
|
||||
/// Calling this method may invalidate all saved indices of this
|
||||
/// `CircularBuffer`. Do not rely on a previously stored index value after
|
||||
/// altering a `CircularBuffer` with any operation that can change its length.
|
||||
///
|
||||
/// - Parameter k: The number of elements to remove from the `CircularBuffer`.
|
||||
/// `k` must be greater than or equal to zero and must not exceed the
|
||||
/// number of elements in the `CircularBuffer`.
|
||||
///
|
||||
/// - Complexity: O(*k*), where *k* is the specified number of elements.
|
||||
@inlinable
|
||||
public mutating func removeLast(_ k: Int) {
|
||||
precondition(k <= self.count, "Number of elements to drop bigger than the amount of elements in the buffer.")
|
||||
var idx = self.tailBackingIndex
|
||||
for _ in 0 ..< k {
|
||||
idx = self.indexAdvanced(index: idx, by: -1)
|
||||
self._buffer[idx] = nil
|
||||
}
|
||||
self.tailBackingIndex = idx
|
||||
}
|
||||
|
||||
|
||||
/// Removes the specified number of elements from the beginning of the
|
||||
/// `CircularBuffer`.
|
||||
///
|
||||
/// Calling this method may invalidate any existing indices for use with this
|
||||
/// `CircularBuffer`.
|
||||
///
|
||||
/// - Parameter k: The number of elements to remove.
|
||||
/// `k` must be greater than or equal to zero and must not exceed the
|
||||
/// number of elements in the `CircularBuffer`.
|
||||
///
|
||||
/// - Complexity: O(*k*), where *k* is the specified number of elements.
|
||||
@inlinable
|
||||
public mutating func removeFirst(_ k: Int) {
|
||||
precondition(k <= self.count, "Number of elements to drop bigger than the amount of elements in the buffer.")
|
||||
var idx = self.headBackingIndex
|
||||
for _ in 0 ..< k {
|
||||
self._buffer[idx] = nil
|
||||
idx = self.indexAdvanced(index: idx, by: 1)
|
||||
}
|
||||
self.headBackingIndex = idx
|
||||
}
|
||||
|
||||
/// Removes and returns the first element of the `CircularBuffer`.
|
||||
///
|
||||
/// The `CircularBuffer` must not be empty.
|
||||
///
|
||||
/// Calling this method may invalidate any existing indices for use with this
|
||||
/// `CircularBuffer`.
|
||||
///
|
||||
/// - Returns: The removed element.
|
||||
///
|
||||
/// - Complexity: O(*1*)
|
||||
@discardableResult
|
||||
@inlinable
|
||||
public mutating func removeFirst() -> Element {
|
||||
defer {
|
||||
self.removeFirst(1)
|
||||
}
|
||||
return self.first!
|
||||
}
|
||||
|
||||
/// Removes and returns the last element of the `CircularBuffer`.
|
||||
///
|
||||
/// The `CircularBuffer` must not be empty.
|
||||
///
|
||||
/// Calling this method may invalidate all saved indices of this
|
||||
/// `CircularBuffer`. Do not rely on a previously stored index value after
|
||||
/// altering the `CircularBuffer` with any operation that can change its length.
|
||||
///
|
||||
/// - Returns: The last element of the `CircularBuffer`.
|
||||
///
|
||||
/// - Complexity: O(*1*)
|
||||
@discardableResult
|
||||
@inlinable
|
||||
public mutating func removeLast() -> Element {
|
||||
defer {
|
||||
self.removeLast(1)
|
||||
}
|
||||
return self.last!
|
||||
}
|
||||
|
||||
/// Replaces the specified subrange of elements with the given `CircularBuffer`.
|
||||
///
|
||||
/// - Parameter subrange: The subrange of the collection to replace. The bounds of the range must be valid indices
|
||||
/// of the `CircularBuffer`.
|
||||
///
|
||||
/// - Parameter newElements: The new elements to add to the `CircularBuffer`.
|
||||
///
|
||||
/// *O(n)* where _n_ is the length of the new elements collection if the subrange equals to _n_
|
||||
///
|
||||
/// *O(m)* where _m_ is the combined length of the collection and _newElements_
|
||||
public mutating func replaceSubrange<C>(_ subrange: Range<Index>, with newElements: C) where C : Collection, Element == C.Element {
|
||||
@inlinable
|
||||
public mutating func replaceSubrange<C: Collection>(_ subrange: Range<Index>, with newElements: C) where Element == C.Element {
|
||||
precondition(subrange.lowerBound >= self.startIndex && subrange.upperBound <= self.endIndex, "Subrange out of bounds")
|
||||
|
||||
if subrange.count == newElements.count {
|
||||
for (index, element) in zip(subrange, newElements) {
|
||||
self.buffer[index.backingIndex] = element
|
||||
let subrangeCount = self.distance(from: subrange.lowerBound, to: subrange.upperBound)
|
||||
|
||||
if subrangeCount == newElements.count {
|
||||
var index = subrange.lowerBound
|
||||
for element in newElements {
|
||||
self._buffer[index.backingIndex] = element
|
||||
index = self.index(after: index)
|
||||
}
|
||||
} else if subrange.count == self.count && newElements.isEmpty {
|
||||
} else if subrangeCount == self.count && newElements.isEmpty {
|
||||
self.removeSubrange(subrange)
|
||||
} else {
|
||||
var newBuffer: ContiguousArray<Element?> = []
|
||||
let neededNewCapacity = self.count + newElements.count - subrange.count + 1 /* always one spare */
|
||||
let neededNewCapacity = self.count + newElements.count - subrangeCount + 1 /* always one spare */
|
||||
let newCapacity = Swift.max(self.capacity, neededNewCapacity.nextPowerOf2())
|
||||
newBuffer.reserveCapacity(newCapacity)
|
||||
|
||||
|
@ -301,38 +538,30 @@ extension CircularBuffer: BidirectionalCollection, RandomAccessCollection, Range
|
|||
if repetitionCount > 0 {
|
||||
newBuffer.append(contentsOf: repeatElement(nil, count: repetitionCount))
|
||||
}
|
||||
self.buffer = newBuffer
|
||||
self.headIdx = Index(backingIndex: 0, backingIndexOfHead: 0, backingCount: newBuffer.count)
|
||||
self.tailIdx = Index(backingIndex: newBuffer.count - repetitionCount, backingIndexOfHead: self.headIdx.backingIndex, backingCount: newBuffer.count)
|
||||
self._buffer = newBuffer
|
||||
self.headBackingIndex = 0
|
||||
self.tailBackingIndex = newBuffer.count - repetitionCount
|
||||
}
|
||||
assert(self.verifyInvariants())
|
||||
}
|
||||
|
||||
/// Removes the elements in the specified subrange from the circular buffer.
|
||||
///
|
||||
/// - Parameter bounds: The range of the circular buffer to be removed. The bounds of the range must be valid indices of the collection.
|
||||
@inlinable
|
||||
public mutating func removeSubrange(_ bounds: Range<Index>) {
|
||||
precondition(bounds.upperBound >= self.startIndex && bounds.upperBound <= self.endIndex, "Invalid bounds.")
|
||||
switch bounds.count {
|
||||
|
||||
let boundsCount = self.distance(from: bounds.lowerBound, to: bounds.upperBound)
|
||||
switch boundsCount {
|
||||
case 1:
|
||||
remove(at: bounds.lowerBound)
|
||||
case self.count:
|
||||
self = .init(initialCapacity: self.buffer.count)
|
||||
self = .init(initialCapacity: self._buffer.count)
|
||||
default:
|
||||
replaceSubrange(bounds, with: [])
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the given number of elements from the end of the collection.
|
||||
///
|
||||
/// - Parameter n: The number of elements to remove from the tail of the buffer.
|
||||
public mutating func removeLast(_ n: Int) {
|
||||
precondition(n <= self.count, "Number of elements to drop bigger than the amount of elements in the buffer.")
|
||||
var idx = self.tailIdx
|
||||
for _ in 0 ..< n {
|
||||
self.buffer[idx.backingIndex] = nil
|
||||
idx = self.index(before: idx)
|
||||
}
|
||||
self.tailIdx = self.tailIdx.advanced(by: -n)
|
||||
assert(self.verifyInvariants())
|
||||
}
|
||||
|
||||
/// Removes & returns the item at `position` from the buffer
|
||||
|
@ -343,31 +572,64 @@ extension CircularBuffer: BidirectionalCollection, RandomAccessCollection, Range
|
|||
/// otherwise
|
||||
/// *O(n)* where *n* is the number of elements between `position` and `tailIdx`.
|
||||
@discardableResult
|
||||
@inlinable
|
||||
public mutating func remove(at position: Index) -> Element {
|
||||
defer {
|
||||
assert(self.verifyInvariants())
|
||||
}
|
||||
precondition(self.indices.contains(position), "Position out of bounds.")
|
||||
var bufferIndex = position
|
||||
let element = self.buffer[bufferIndex.backingIndex]!
|
||||
var bufferIndex = position.backingIndex
|
||||
let element = self._buffer[bufferIndex]!
|
||||
|
||||
switch bufferIndex {
|
||||
case self.headIdx:
|
||||
self.headIdx = self.headIdx.advanced(by: 1)
|
||||
self.headIdx.isIndexGEQHeadIndex = true
|
||||
self.tailIdx.isIndexGEQHeadIndex = self.tailIdx.backingIndex >= self.headIdx.backingIndex
|
||||
self.buffer[bufferIndex.backingIndex] = nil
|
||||
case self.index(before: self.tailIdx):
|
||||
self.tailIdx = self.index(before: self.tailIdx)
|
||||
self.buffer[bufferIndex.backingIndex] = nil
|
||||
case self.headBackingIndex:
|
||||
self.advanceHeadIdx(by: 1)
|
||||
self._buffer[bufferIndex] = nil
|
||||
case self.indexBeforeHeadIdx():
|
||||
self.advanceTailIdx(by: -1)
|
||||
self.tailBackingIndex = self.indexBeforeTailIdx()
|
||||
self._buffer[bufferIndex] = nil
|
||||
default:
|
||||
var nextIndex = self.index(after: bufferIndex)
|
||||
while nextIndex != self.tailIdx {
|
||||
self.buffer[bufferIndex.backingIndex] = self.buffer[nextIndex.backingIndex]
|
||||
self._buffer[bufferIndex] = nil
|
||||
var nextIndex = self.indexAdvanced(index: bufferIndex, by: 1)
|
||||
while nextIndex != self.tailBackingIndex {
|
||||
self._buffer.swapAt(bufferIndex, nextIndex)
|
||||
bufferIndex = nextIndex
|
||||
nextIndex = self.index(after: bufferIndex)
|
||||
nextIndex = self.indexAdvanced(index: bufferIndex, by: 1)
|
||||
}
|
||||
self.buffer[nextIndex.backingIndex] = nil
|
||||
self.tailIdx = self.index(before: self.tailIdx)
|
||||
self.advanceTailIdx(by: -1)
|
||||
}
|
||||
|
||||
return element
|
||||
}
|
||||
}
|
||||
|
||||
extension CircularBuffer {
|
||||
@usableFromInline
|
||||
internal func verifyInvariants() -> Bool {
|
||||
var index = self.headBackingIndex
|
||||
while index != self.tailBackingIndex {
|
||||
if self._buffer[index] == nil {
|
||||
return false
|
||||
}
|
||||
index = self.indexAdvanced(index: index, by: 1)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// this is not a general invariant (not true for CircularBuffer that have been sliced)
|
||||
private func unreachableAreNil() -> Bool {
|
||||
var index = self.tailBackingIndex
|
||||
while index != self.headBackingIndex {
|
||||
if self._buffer[index] != nil {
|
||||
return false
|
||||
}
|
||||
index = self.indexAdvanced(index: index, by: 1)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
internal func testOnly_verifyInvariantsForNonSlices() -> Bool {
|
||||
return self.verifyInvariants() && self.unreachableAreNil()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,10 +17,7 @@
|
|||
/// This object is used extensively within SwiftNIO to handle flushable buffers. It can be used to store buffered
|
||||
/// writes and mark how far through the buffer the user has flushed, and therefore how far through the buffer is
|
||||
/// safe to write.
|
||||
public struct MarkedCircularBuffer<Element>: CustomStringConvertible, AppendableCollection {
|
||||
public typealias RangeType<Bound> = Range<Bound> where Bound: Strideable, Bound.Stride: SignedInteger
|
||||
public typealias Index = CircularBuffer<Element>.Index
|
||||
|
||||
public struct MarkedCircularBuffer<Element>: CustomStringConvertible {
|
||||
private var buffer: CircularBuffer<Element>
|
||||
private var markedIndexOffset: Int? = nil /* nil: nothing marked */
|
||||
|
||||
|
@ -41,17 +38,19 @@ public struct MarkedCircularBuffer<Element>: CustomStringConvertible, Appendable
|
|||
|
||||
/// Removes the first element from the buffer.
|
||||
public mutating func removeFirst() -> Element {
|
||||
return self.popFirst()!
|
||||
}
|
||||
|
||||
public mutating func popFirst() -> Element? {
|
||||
assert(self.buffer.count > 0)
|
||||
if let markedIndex = self.markedIndexOffset {
|
||||
if self.startIndex.advanced(by: markedIndex) == self.startIndex {
|
||||
self.markedIndexOffset = nil
|
||||
if let markedIndexOffset = self.markedIndexOffset {
|
||||
if markedIndexOffset > 0 {
|
||||
self.markedIndexOffset = markedIndexOffset - 1
|
||||
} else {
|
||||
if let markedIndexOffset = self.markedIndexOffset {
|
||||
self.markedIndexOffset = markedIndexOffset - 1
|
||||
}
|
||||
self.markedIndexOffset = nil
|
||||
}
|
||||
}
|
||||
return self.buffer.removeFirst()
|
||||
return self.buffer.popFirst()
|
||||
}
|
||||
|
||||
/// The first element in the buffer.
|
||||
|
@ -69,29 +68,6 @@ public struct MarkedCircularBuffer<Element>: CustomStringConvertible, Appendable
|
|||
return self.buffer.count
|
||||
}
|
||||
|
||||
/// Retrieves the element at the given index from the buffer, without removing it.
|
||||
public subscript(index: Index) -> Element {
|
||||
get {
|
||||
return self.buffer[index]
|
||||
}
|
||||
set {
|
||||
self.buffer[index] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
/// The valid indices into the buffer.
|
||||
public var indices: RangeType<Index> {
|
||||
return self.buffer.indices
|
||||
}
|
||||
|
||||
public var startIndex: Index { return self.buffer.startIndex }
|
||||
|
||||
public var endIndex: Index { return self.buffer.endIndex }
|
||||
|
||||
public func index(after i: Index) -> Index {
|
||||
return self.buffer.index(after: i)
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
return self.buffer.description
|
||||
}
|
||||
|
@ -113,7 +89,7 @@ public struct MarkedCircularBuffer<Element>: CustomStringConvertible, Appendable
|
|||
assert(index >= self.startIndex, "index must not be negative")
|
||||
precondition(index < self.endIndex, "index \(index) out of range (0..<\(self.buffer.count))")
|
||||
if let markedIndexOffset = self.markedIndexOffset {
|
||||
return self.startIndex.advanced(by: markedIndexOffset) == index
|
||||
return self.index(self.startIndex, offsetBy: markedIndexOffset) == index
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
@ -123,7 +99,7 @@ public struct MarkedCircularBuffer<Element>: CustomStringConvertible, Appendable
|
|||
public var markedElementIndex: Index? {
|
||||
if let markedIndexOffset = self.markedIndexOffset {
|
||||
assert(markedIndexOffset >= 0)
|
||||
return self.startIndex.advanced(by: markedIndexOffset)
|
||||
return self.index(self.startIndex, offsetBy: markedIndexOffset)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
@ -139,3 +115,48 @@ public struct MarkedCircularBuffer<Element>: CustomStringConvertible, Appendable
|
|||
return self.markedIndexOffset != nil
|
||||
}
|
||||
}
|
||||
|
||||
extension MarkedCircularBuffer: Collection, MutableCollection {
|
||||
public typealias RangeType<Bound> = Range<Bound> where Bound: Strideable, Bound.Stride: SignedInteger
|
||||
public typealias Index = CircularBuffer<Element>.Index
|
||||
public typealias SubSequence = CircularBuffer<Element>
|
||||
|
||||
public func index(after i: Index) -> Index {
|
||||
return self.buffer.index(after: i)
|
||||
}
|
||||
|
||||
public var startIndex: Index { return self.buffer.startIndex }
|
||||
|
||||
public var endIndex: Index { return self.buffer.endIndex }
|
||||
|
||||
/// Retrieves the element at the given index from the buffer, without removing it.
|
||||
public subscript(index: Index) -> Element {
|
||||
get {
|
||||
return self.buffer[index]
|
||||
}
|
||||
set {
|
||||
self.buffer[index] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
public subscript(bounds: Range<Index>) -> SubSequence {
|
||||
get {
|
||||
return self.buffer[bounds]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MarkedCircularBuffer: RandomAccessCollection {
|
||||
public func index(_ i: Index, offsetBy distance: Int) -> Index {
|
||||
return self.buffer.index(i, offsetBy: distance)
|
||||
}
|
||||
|
||||
public func distance(from start: Index, to end: Index) -> Int {
|
||||
return self.buffer.distance(from: start, to: end)
|
||||
}
|
||||
|
||||
public func index(before i: Index) -> Index {
|
||||
return self.buffer.index(before: i)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -295,10 +295,11 @@ private struct PendingDatagramWritesState {
|
|||
/// Returns the best mechanism to write pending data at the current point in time.
|
||||
var currentBestWriteMechanism: WriteMechanism {
|
||||
switch self.pendingWrites.markedElementIndex {
|
||||
case .some(let e) where self.pendingWrites.startIndex.distance(to: e) > 0:
|
||||
case .some(let e) where self.pendingWrites.distance(from: self.pendingWrites.startIndex, to: e) > 0:
|
||||
return .vectorBufferWrite
|
||||
case .some(let e):
|
||||
assert(e.distance(to: self.pendingWrites.startIndex) == 0) // The compiler can't prove this, but it must be so.
|
||||
// The compiler can't prove this, but it must be so.
|
||||
assert(self.pendingWrites.distance(from: e, to: self.pendingWrites.startIndex) == 0)
|
||||
return .scalarBufferWrite
|
||||
default:
|
||||
return .nothingToBeWritten
|
||||
|
@ -320,9 +321,10 @@ extension PendingDatagramWritesState {
|
|||
}
|
||||
|
||||
mutating func next() -> PendingDatagramWrite? {
|
||||
while let markedIndex = self.markedIndex, self.index.distance(to: markedIndex) >= 0 {
|
||||
while let markedIndex = self.markedIndex, self.pendingWrites.pendingWrites.distance(from: self.index,
|
||||
to: markedIndex) >= 0 {
|
||||
let element = self.pendingWrites.pendingWrites[index]
|
||||
index = index.advanced(by: 1)
|
||||
index = self.pendingWrites.pendingWrites.index(after: index)
|
||||
return element
|
||||
}
|
||||
|
||||
|
|
|
@ -106,7 +106,9 @@ private struct PendingStreamWritesState {
|
|||
public private(set) var bytes: Int64 = 0
|
||||
|
||||
public var flushedChunks: Int {
|
||||
return self.pendingWrites.markedElementIndex.map { self.pendingWrites.startIndex.distance(to: $0) + 1 } ?? 0
|
||||
return self.pendingWrites.markedElementIndex.map {
|
||||
self.pendingWrites.distance(from: self.pendingWrites.startIndex, to: $0) + 1
|
||||
} ?? 0
|
||||
}
|
||||
|
||||
/// Subtract `bytes` from the number of outstanding bytes to write.
|
||||
|
@ -162,7 +164,7 @@ private struct PendingStreamWritesState {
|
|||
|
||||
/// Get the outstanding write at `index`.
|
||||
public subscript(index: Int) -> PendingStreamWrite {
|
||||
return self.pendingWrites[self.pendingWrites.startIndex.advanced(by: index)]
|
||||
return self.pendingWrites[self.pendingWrites.index(self.pendingWrites.startIndex, offsetBy: index)]
|
||||
}
|
||||
|
||||
/// Mark the flush checkpoint.
|
||||
|
@ -253,7 +255,8 @@ private struct PendingStreamWritesState {
|
|||
}
|
||||
default:
|
||||
let startIndex = self.pendingWrites.startIndex
|
||||
switch (self.pendingWrites[startIndex].data, self.pendingWrites[startIndex.advanced(by: 1)].data) {
|
||||
switch (self.pendingWrites[startIndex].data,
|
||||
self.pendingWrites[self.pendingWrites.index(after: startIndex)].data) {
|
||||
case (.byteBuffer, .byteBuffer):
|
||||
return .vectorBufferWrite
|
||||
case (.byteBuffer, .fileRegion):
|
||||
|
|
|
@ -260,7 +260,7 @@ extension MarkedCircularBuffer {
|
|||
public typealias E = Element
|
||||
|
||||
func _makeIndex(value: Int) -> Index {
|
||||
return self.startIndex.advanced(by: value)
|
||||
return self.index(self.startIndex, offsetBy: value)
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "init(initialCapacity:)")
|
||||
|
@ -377,7 +377,7 @@ extension SocketAddress {
|
|||
|
||||
extension CircularBuffer {
|
||||
func _makeIndex(value: Int) -> Index {
|
||||
return self.startIndex.advanced(by: value)
|
||||
return self.index(self.startIndex, offsetBy: value)
|
||||
}
|
||||
|
||||
@available(*, deprecated, renamed: "Element")
|
||||
|
|
|
@ -65,6 +65,13 @@ extension CircularBufferTests {
|
|||
("testIntIndexing", testIntIndexing),
|
||||
("testIndexDistance", testIndexDistance),
|
||||
("testIndexAdvancing", testIndexAdvancing),
|
||||
("testPopFirst", testPopFirst),
|
||||
("testSlicing", testSlicing),
|
||||
("testRemoveInMiddle", testRemoveInMiddle),
|
||||
("testLotsOfPrepending", testLotsOfPrepending),
|
||||
("testLotsOfInsertAtStart", testLotsOfInsertAtStart),
|
||||
("testLotsOfInsertAtEnd", testLotsOfInsertAtEnd),
|
||||
("testPopLast", testPopLast),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,9 @@ class CircularBufferTests: XCTestCase {
|
|||
func testTrivial() {
|
||||
var ring = CircularBuffer<Int>(initialCapacity: 8)
|
||||
ring.append(1)
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
XCTAssertEqual(1, ring.removeFirst())
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
}
|
||||
|
||||
func testAddRemoveInALoop() {
|
||||
|
@ -43,10 +45,12 @@ class CircularBufferTests: XCTestCase {
|
|||
for f in 1..<1000 {
|
||||
ring.append(f)
|
||||
XCTAssertEqual(f, ring.count)
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
}
|
||||
for f in 1..<1000 {
|
||||
XCTAssertEqual(f, ring.removeFirst())
|
||||
XCTAssertEqual(999 - f, ring.count)
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
}
|
||||
XCTAssertTrue(ring.isEmpty)
|
||||
}
|
||||
|
@ -55,10 +59,12 @@ class CircularBufferTests: XCTestCase {
|
|||
var ring = CircularBuffer<Int>(initialCapacity: 4)
|
||||
for idx in 0..<7 {
|
||||
ring.prepend(idx)
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
}
|
||||
|
||||
XCTAssertEqual(7, ring.count)
|
||||
_ = ring.remove(at: ring.startIndex.advanced(by: 1))
|
||||
_ = ring.remove(at: ring.index(ring.startIndex, offsetBy: 1))
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
XCTAssertEqual(6, ring.count)
|
||||
XCTAssertEqual(0, ring.last)
|
||||
}
|
||||
|
@ -69,7 +75,7 @@ class CircularBufferTests: XCTestCase {
|
|||
ring.prepend(idx)
|
||||
}
|
||||
|
||||
let last = ring.remove(at: ring.endIndex.advanced(by: -1))
|
||||
let last = ring.remove(at: ring.index(ring.endIndex, offsetBy: -1))
|
||||
XCTAssertEqual(0, last)
|
||||
XCTAssertEqual(1, ring.last)
|
||||
}
|
||||
|
@ -79,7 +85,8 @@ class CircularBufferTests: XCTestCase {
|
|||
ring.prepend(99)
|
||||
ring.prepend(98)
|
||||
XCTAssertEqual(2, ring.count)
|
||||
XCTAssertEqual(99, ring.remove(at: ring.endIndex.advanced(by: -1)))
|
||||
XCTAssertEqual(99, ring.remove(at: ring.index(ring.endIndex, offsetBy: -1)))
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
XCTAssertFalse(ring.isEmpty)
|
||||
XCTAssertEqual(1, ring.count)
|
||||
XCTAssertEqual(98, ring.last)
|
||||
|
@ -97,85 +104,131 @@ class CircularBufferTests: XCTestCase {
|
|||
XCTAssertEqual(5, ring.first)
|
||||
}
|
||||
|
||||
func collectAllIndices<Element>(ring: CircularBuffer<Element>) -> [CircularBuffer<Element>.Index] {
|
||||
return Array(ring.indices)
|
||||
}
|
||||
|
||||
func collectAllIndices<Element>(ring: CircularBuffer<Element>, range: Range<CircularBuffer<Element>.Index>) -> [CircularBuffer<Element>.Index] {
|
||||
var index: CircularBuffer<Element>.Index = range.lowerBound
|
||||
var allIndices: [CircularBuffer<Element>.Index] = []
|
||||
while index != range.upperBound {
|
||||
allIndices.append(index)
|
||||
index = ring.index(after: index)
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
}
|
||||
return allIndices
|
||||
}
|
||||
|
||||
func testHarderExpansion() {
|
||||
var ring = CircularBuffer<Int>(initialCapacity: 3)
|
||||
XCTAssertEqual(ring.indices, ring.startIndex ..< ring.startIndex)
|
||||
XCTAssertEqual(self.collectAllIndices(ring: ring),
|
||||
self.collectAllIndices(ring: ring, range: ring.startIndex ..< ring.startIndex))
|
||||
|
||||
ring.append(1)
|
||||
XCTAssertEqual(ring.count, 1)
|
||||
XCTAssertEqual(ring[ring.startIndex], 1)
|
||||
XCTAssertEqual(ring.indices, ring.startIndex ..< ring.startIndex.advanced(by: 1))
|
||||
XCTAssertEqual(self.collectAllIndices(ring: ring),
|
||||
self.collectAllIndices(ring: ring, range: ring.startIndex ..< ring.index(ring.startIndex,
|
||||
offsetBy: 1)))
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
|
||||
ring.append(2)
|
||||
XCTAssertEqual(ring.count, 2)
|
||||
XCTAssertEqual(ring[ring.startIndex], 1)
|
||||
XCTAssertEqual(ring[ring.startIndex.advanced(by: 1)], 2)
|
||||
XCTAssertEqual(ring.indices, ring.startIndex ..< ring.startIndex.advanced(by: 2))
|
||||
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 1)], 2)
|
||||
XCTAssertEqual(self.collectAllIndices(ring: ring),
|
||||
self.collectAllIndices(ring: ring, range: ring.startIndex ..< ring.index(ring.startIndex,
|
||||
offsetBy: 2)))
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
|
||||
ring.append(3)
|
||||
XCTAssertEqual(ring.count, 3)
|
||||
XCTAssertEqual(ring[ring.startIndex], 1)
|
||||
XCTAssertEqual(ring[ring.startIndex.advanced(by: 1)], 2)
|
||||
XCTAssertEqual(ring[ring.startIndex.advanced(by: 2)], 3)
|
||||
XCTAssertEqual(ring.indices, ring.startIndex ..< ring.startIndex.advanced(by: 3))
|
||||
|
||||
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 1)], 2)
|
||||
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 2)], 3)
|
||||
XCTAssertEqual(self.collectAllIndices(ring: ring),
|
||||
self.collectAllIndices(ring: ring, range: ring.startIndex ..< ring.index(ring.startIndex,
|
||||
offsetBy: 3)))
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
|
||||
XCTAssertEqual(1, ring.removeFirst())
|
||||
XCTAssertEqual(ring.count, 2)
|
||||
XCTAssertEqual(ring[ring.startIndex], 2)
|
||||
XCTAssertEqual(ring[ring.startIndex.advanced(by: 1)], 3)
|
||||
XCTAssertEqual(ring.indices, ring.startIndex ..< ring.startIndex.advanced(by: 2))
|
||||
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 1)], 3)
|
||||
XCTAssertEqual(self.collectAllIndices(ring: ring),
|
||||
self.collectAllIndices(ring: ring, range: ring.startIndex ..< ring.index(ring.startIndex,
|
||||
offsetBy: 2)))
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
|
||||
XCTAssertEqual(2, ring.removeFirst())
|
||||
XCTAssertEqual(ring.count, 1)
|
||||
XCTAssertEqual(ring[ring.startIndex], 3)
|
||||
XCTAssertEqual(ring.indices, ring.startIndex ..< ring.startIndex.advanced(by: 1))
|
||||
XCTAssertEqual(self.collectAllIndices(ring: ring),
|
||||
self.collectAllIndices(ring: ring, range: ring.startIndex ..< ring.index(ring.startIndex,
|
||||
offsetBy: 1)))
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
|
||||
ring.append(5)
|
||||
XCTAssertEqual(ring.count, 2)
|
||||
XCTAssertEqual(ring[ring.startIndex], 3)
|
||||
XCTAssertEqual(ring[ring.startIndex.advanced(by: 1)], 5)
|
||||
XCTAssertEqual(ring.indices, ring.startIndex ..< ring.startIndex.advanced(by: 2))
|
||||
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 1)], 5)
|
||||
XCTAssertEqual(self.collectAllIndices(ring: ring),
|
||||
self.collectAllIndices(ring: ring, range: ring.startIndex ..< ring.index(ring.startIndex,
|
||||
offsetBy: 2)))
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
|
||||
ring.append(6)
|
||||
XCTAssertEqual(ring.count, 3)
|
||||
XCTAssertEqual(ring[ring.startIndex], 3)
|
||||
XCTAssertEqual(ring[ring.startIndex.advanced(by: 1)], 5)
|
||||
XCTAssertEqual(ring[ring.startIndex.advanced(by: 2)], 6)
|
||||
XCTAssertEqual(ring.indices, ring.startIndex ..< ring.startIndex.advanced(by: 3))
|
||||
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 1)], 5)
|
||||
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 2)], 6)
|
||||
XCTAssertEqual(self.collectAllIndices(ring: ring),
|
||||
self.collectAllIndices(ring: ring, range: ring.startIndex ..< ring.index(ring.startIndex,
|
||||
offsetBy: 3)))
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
|
||||
ring.append(7)
|
||||
XCTAssertEqual(ring.count, 4)
|
||||
XCTAssertEqual(ring[ring.startIndex], 3)
|
||||
XCTAssertEqual(ring[ring.startIndex.advanced(by: 1)], 5)
|
||||
XCTAssertEqual(ring[ring.startIndex.advanced(by: 2)], 6)
|
||||
XCTAssertEqual(ring[ring.startIndex.advanced(by: 3)], 7)
|
||||
XCTAssertEqual(ring.indices, ring.startIndex ..< ring.startIndex.advanced(by: 4))
|
||||
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 1)], 5)
|
||||
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 2)], 6)
|
||||
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 3)], 7)
|
||||
XCTAssertEqual(self.collectAllIndices(ring: ring),
|
||||
self.collectAllIndices(ring: ring, range: ring.startIndex ..< ring.index(ring.startIndex,
|
||||
offsetBy: 4)))
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
}
|
||||
|
||||
func testCollection() {
|
||||
var ring = CircularBuffer<Int>(initialCapacity: 4)
|
||||
XCTAssertEqual(ring.indices, ring.startIndex ..< ring.startIndex)
|
||||
XCTAssertEqual(0, ring.startIndex.distance(to: ring.endIndex))
|
||||
XCTAssertEqual(0, ring.endIndex.distance(to: ring.startIndex))
|
||||
XCTAssertEqual(self.collectAllIndices(ring: ring),
|
||||
self.collectAllIndices(ring: ring, range: ring.startIndex ..< ring.startIndex))
|
||||
XCTAssertEqual(0, ring.distance(from: ring.startIndex, to: ring.endIndex))
|
||||
XCTAssertEqual(0, ring.distance(from: ring.startIndex, to: ring.startIndex))
|
||||
|
||||
for idx in 0..<5 {
|
||||
ring.append(idx)
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
}
|
||||
|
||||
XCTAssertFalse(ring.isEmpty)
|
||||
XCTAssertEqual(5, ring.count)
|
||||
|
||||
XCTAssertEqual(ring.indices, ring.startIndex ..< ring.startIndex.advanced(by: 5))
|
||||
XCTAssertEqual(self.collectAllIndices(ring: ring),
|
||||
self.collectAllIndices(ring: ring, range: ring.startIndex ..< ring.index(ring.startIndex,
|
||||
offsetBy: 5)))
|
||||
XCTAssertEqual(ring.startIndex, ring.startIndex)
|
||||
XCTAssertEqual(ring.endIndex, ring.startIndex.advanced(by: 5))
|
||||
XCTAssertEqual(ring.endIndex, ring.index(ring.startIndex, offsetBy: 5))
|
||||
|
||||
XCTAssertEqual(ring.index(after: ring.startIndex.advanced(by: 1)), ring.startIndex.advanced(by: 2))
|
||||
XCTAssertEqual(ring.index(before: ring.startIndex.advanced(by: 3)), ring.startIndex.advanced(by: 2))
|
||||
XCTAssertEqual(ring.index(after: ring.index(ring.startIndex, offsetBy: 1)),
|
||||
ring.index(ring.startIndex, offsetBy: 2))
|
||||
XCTAssertEqual(ring.index(before: ring.index(ring.startIndex, offsetBy: 3)),
|
||||
ring.index(ring.startIndex, offsetBy: 2))
|
||||
|
||||
let actualValues = [Int](ring)
|
||||
let expectedValues = [0, 1, 2, 3, 4]
|
||||
XCTAssertEqual(expectedValues, actualValues)
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
}
|
||||
|
||||
func testReplaceSubrange5ElementsWith1() {
|
||||
|
@ -184,12 +237,14 @@ class CircularBufferTests: XCTestCase {
|
|||
ring.prepend(idx)
|
||||
}
|
||||
XCTAssertEqual(50, ring.count)
|
||||
ring.replaceSubrange(ring.startIndex.advanced(by: 20) ..< ring.startIndex.advanced(by: 25), with: [99])
|
||||
ring.replaceSubrange(ring.index(ring.startIndex, offsetBy: 20) ..< ring.index(ring.startIndex, offsetBy: 25),
|
||||
with: [99])
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
|
||||
XCTAssertEqual(ring.count, 46)
|
||||
XCTAssertEqual(ring[ring.startIndex.advanced(by: 19)], 30)
|
||||
XCTAssertEqual(ring[ring.startIndex.advanced(by: 20)], 99)
|
||||
XCTAssertEqual(ring[ring.startIndex.advanced(by: 21)], 24)
|
||||
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 19)], 30)
|
||||
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 20)], 99)
|
||||
XCTAssertEqual(ring[ring.index(ring.startIndex, offsetBy: 21)], 24)
|
||||
}
|
||||
|
||||
func testReplaceSubrangeAllElementsWithFewerElements() {
|
||||
|
@ -200,6 +255,7 @@ class CircularBufferTests: XCTestCase {
|
|||
XCTAssertEqual(50, ring.count)
|
||||
|
||||
ring.replaceSubrange(ring.startIndex..<ring.endIndex, with: [3,4])
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
XCTAssertEqual(2, ring.count)
|
||||
XCTAssertEqual(3, ring.first)
|
||||
XCTAssertEqual(4, ring.last)
|
||||
|
@ -213,6 +269,7 @@ class CircularBufferTests: XCTestCase {
|
|||
XCTAssertEqual(50, ring.count)
|
||||
|
||||
ring.replaceSubrange(ring.startIndex ..< ring.startIndex, with: [])
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
XCTAssertEqual(50, ring.count)
|
||||
}
|
||||
|
||||
|
@ -224,6 +281,7 @@ class CircularBufferTests: XCTestCase {
|
|||
XCTAssertEqual(5, ring.count)
|
||||
|
||||
ring.replaceSubrange(ring.startIndex..<ring.endIndex, with: [10,11,12,13,14,15,16,17,18,19])
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
XCTAssertEqual(10, ring.count)
|
||||
XCTAssertEqual(10, ring.first)
|
||||
XCTAssertEqual(19, ring.last)
|
||||
|
@ -247,6 +305,7 @@ class CircularBufferTests: XCTestCase {
|
|||
XCTAssertEqual(4, replacement.last)
|
||||
|
||||
ring.replaceSubrange(ring.startIndex..<ring.endIndex, with: replacement)
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
XCTAssertEqual(5, ring.count)
|
||||
XCTAssertEqual(0, ring.first)
|
||||
XCTAssertEqual(4, ring.last)
|
||||
|
@ -262,6 +321,7 @@ class CircularBufferTests: XCTestCase {
|
|||
XCTAssertEqual(0, ring.last)
|
||||
|
||||
ring.replaceSubrange(ring.startIndex..<ring.endIndex, with: [])
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
XCTAssertTrue(ring.isEmpty)
|
||||
}
|
||||
|
||||
|
@ -270,6 +330,7 @@ class CircularBufferTests: XCTestCase {
|
|||
XCTAssertTrue(ring.isEmpty)
|
||||
for idx in 0..<4 {
|
||||
ring.append(idx)
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
}
|
||||
XCTAssertEqual(4, ring.count)
|
||||
XCTAssertFalse(ring.isEmpty)
|
||||
|
@ -281,7 +342,7 @@ class CircularBufferTests: XCTestCase {
|
|||
ring.append(idx)
|
||||
}
|
||||
for idx in 0..<5 {
|
||||
XCTAssertEqual(idx, ring[ring.startIndex.advanced(by: idx)])
|
||||
XCTAssertEqual(idx, ring[ring.index(ring.startIndex, offsetBy: idx)])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -292,23 +353,23 @@ class CircularBufferTests: XCTestCase {
|
|||
}
|
||||
/* the underlying buffer should now be filled from 0 to max */
|
||||
for idx in 0..<4 {
|
||||
XCTAssertEqual(idx, ring[ring.startIndex.advanced(by: idx)])
|
||||
XCTAssertEqual(idx, ring[ring.index(ring.startIndex, offsetBy: idx)])
|
||||
}
|
||||
XCTAssertEqual(0, ring.removeFirst())
|
||||
/* now the first element is gone, ie. the ring starts at index 1 now */
|
||||
for idx in 0..<3 {
|
||||
XCTAssertEqual(idx + 1, ring[ring.startIndex.advanced(by: idx)])
|
||||
XCTAssertEqual(idx + 1, ring[ring.index(ring.startIndex, offsetBy: idx)])
|
||||
}
|
||||
ring.append(4)
|
||||
XCTAssertEqual(1, ring.first!)
|
||||
/* now the last element should be at ring position 0 */
|
||||
for idx in 0..<4 {
|
||||
XCTAssertEqual(idx + 1, ring[ring.startIndex.advanced(by: idx)])
|
||||
XCTAssertEqual(idx + 1, ring[ring.index(ring.startIndex, offsetBy: idx)])
|
||||
}
|
||||
/* and now we'll make it expand */
|
||||
ring.append(5)
|
||||
for idx in 0..<5 {
|
||||
XCTAssertEqual(idx + 1, ring[ring.startIndex.advanced(by: idx)])
|
||||
XCTAssertEqual(idx + 1, ring[ring.index(ring.startIndex, offsetBy: idx)])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -318,7 +379,7 @@ class CircularBufferTests: XCTestCase {
|
|||
ring.append(idx)
|
||||
}
|
||||
for idx in 0..<4 {
|
||||
XCTAssertEqual(idx, ring[ring.startIndex.advanced(by: idx)])
|
||||
XCTAssertEqual(idx, ring[ring.index(ring.startIndex, offsetBy: idx)])
|
||||
}
|
||||
for idx in 0..<4 {
|
||||
XCTAssertEqual(idx, ring.removeFirst())
|
||||
|
@ -336,9 +397,11 @@ class CircularBufferTests: XCTestCase {
|
|||
for (idx, element) in ring.enumerated() {
|
||||
XCTAssertEqual(idx, element)
|
||||
changes.append((idx, element * 2))
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
}
|
||||
for change in changes {
|
||||
ring[ring.startIndex.advanced(by: change.0)] = change.1
|
||||
ring[ring.index(ring.startIndex, offsetBy: change.0)] = change.1
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
}
|
||||
for (idx, element) in ring.enumerated() {
|
||||
XCTAssertEqual(idx * 2, element)
|
||||
|
@ -351,9 +414,10 @@ class CircularBufferTests: XCTestCase {
|
|||
ring.append(idx)
|
||||
}
|
||||
|
||||
let slice = ring[ring.startIndex.advanced(by: 25) ..< ring.startIndex.advanced(by: 30)]
|
||||
let slice = ring[ring.index(ring.startIndex, offsetBy: 25) ..< ring.index(ring.startIndex, offsetBy: 30)]
|
||||
for (idx, element) in slice.enumerated() {
|
||||
XCTAssertEqual(ring.startIndex.advanced(by: idx + 25), ring.startIndex.advanced(by: element))
|
||||
XCTAssertEqual(ring.index(ring.startIndex, offsetBy: idx + 25),
|
||||
ring.index(ring.startIndex, offsetBy: element))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -364,20 +428,25 @@ class CircularBufferTests: XCTestCase {
|
|||
ring.append(2)
|
||||
XCTAssertEqual(2, ring.count)
|
||||
XCTAssertEqual(1, ring.removeFirst())
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
ring.append(3)
|
||||
XCTAssertEqual(2, ring.count)
|
||||
XCTAssertEqual(2, ring.removeFirst())
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
ring.append(4)
|
||||
|
||||
XCTAssertEqual(2, ring.count)
|
||||
XCTAssertEqual(3, ring.removeFirst())
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
ring.append(5)
|
||||
|
||||
XCTAssertEqual(3, ring.headIdx.backingIndex)
|
||||
XCTAssertEqual(1, ring.tailIdx.backingIndex)
|
||||
XCTAssertEqual(3, ring.headBackingIndex)
|
||||
XCTAssertEqual(1, ring.tailBackingIndex)
|
||||
XCTAssertEqual(2, ring.count)
|
||||
XCTAssertEqual(4, ring.removeFirst())
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
XCTAssertEqual(5, ring.removeFirst())
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
XCTAssertEqual(0, ring.count)
|
||||
XCTAssertTrue(ring.isEmpty)
|
||||
}
|
||||
|
@ -388,6 +457,7 @@ class CircularBufferTests: XCTestCase {
|
|||
ring.append(1)
|
||||
XCTAssertEqual(1, ring.first)
|
||||
XCTAssertEqual(1, ring.removeFirst())
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
XCTAssertNil(ring.first)
|
||||
}
|
||||
|
||||
|
@ -397,6 +467,7 @@ class CircularBufferTests: XCTestCase {
|
|||
ring.prepend(1)
|
||||
XCTAssertEqual(1, ring.last)
|
||||
XCTAssertEqual(1, ring.removeLast())
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
XCTAssertNil(ring.last)
|
||||
XCTAssertEqual(0, ring.count)
|
||||
XCTAssertTrue(ring.isEmpty)
|
||||
|
@ -409,6 +480,7 @@ class CircularBufferTests: XCTestCase {
|
|||
ring.prepend(0)
|
||||
XCTAssertEqual(1, ring.last)
|
||||
XCTAssertEqual(1, ring.removeLast())
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
XCTAssertEqual(0, ring.last)
|
||||
}
|
||||
|
||||
|
@ -447,7 +519,9 @@ class CircularBufferTests: XCTestCase {
|
|||
XCTAssertEqual(2, ring.first)
|
||||
|
||||
XCTAssertEqual(1, ring.removeLast())
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
XCTAssertEqual(2, ring.removeFirst())
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
|
||||
XCTAssertNil(ring.last)
|
||||
XCTAssertNil(ring.first)
|
||||
|
@ -463,6 +537,7 @@ class CircularBufferTests: XCTestCase {
|
|||
}
|
||||
for f in 1..<1000 {
|
||||
XCTAssertEqual(f, ring.removeLast())
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
}
|
||||
XCTAssertTrue(ring.isEmpty)
|
||||
XCTAssertEqual(0, ring.count)
|
||||
|
@ -477,6 +552,7 @@ class CircularBufferTests: XCTestCase {
|
|||
XCTAssertEqual(ring.capacity, 4)
|
||||
XCTAssertEqual(ring.count, 2)
|
||||
ring.removeAll(keepingCapacity: true)
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
XCTAssertEqual(ring.capacity, 4)
|
||||
XCTAssertEqual(ring.count, 0)
|
||||
}
|
||||
|
@ -490,6 +566,7 @@ class CircularBufferTests: XCTestCase {
|
|||
XCTAssertEqual(ring.capacity, 4)
|
||||
XCTAssertEqual(ring.count, 2)
|
||||
ring.removeAll(keepingCapacity: false)
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
// 1 is the smallest capacity we have
|
||||
XCTAssertEqual(ring.capacity, 1)
|
||||
XCTAssertEqual(ring.count, 0)
|
||||
|
@ -500,6 +577,7 @@ class CircularBufferTests: XCTestCase {
|
|||
XCTAssertEqual(ring.capacity, 4)
|
||||
XCTAssertEqual(ring.count, 2)
|
||||
ring.removeAll() // default should not keep capacity
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
XCTAssertEqual(ring.capacity, 1)
|
||||
XCTAssertEqual(ring.count, 0)
|
||||
}
|
||||
|
@ -511,7 +589,8 @@ class CircularBufferTests: XCTestCase {
|
|||
|
||||
// Now we want to replace the last subrange with two elements. This should
|
||||
// force an increase in size.
|
||||
ring.replaceSubrange(ring.startIndex ..< ring.startIndex.advanced(by: 1), with: [3, 4])
|
||||
ring.replaceSubrange(ring.startIndex ..< ring.index(ring.startIndex, offsetBy: 1), with: [3, 4])
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
XCTAssertEqual(ring.capacity, 4)
|
||||
}
|
||||
|
||||
|
@ -558,15 +637,21 @@ class CircularBufferTests: XCTestCase {
|
|||
var ring = CircularBuffer<Int>(initialCapacity: 4)
|
||||
|
||||
(0..<16).forEach { ring.append($0) }
|
||||
(0..<4).forEach { _ in ring.removeFirst() }
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
(0..<4).forEach { _ in _ = ring.removeFirst() }
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
(16..<20).forEach { ring.append($0) }
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
XCTAssertEqual(Array(4..<20), Array(ring))
|
||||
|
||||
ring.removeAll(keepingCapacity: shouldKeepCapacity)
|
||||
|
||||
(0..<8).forEach { ring.append($0) }
|
||||
(0..<4).forEach { _ in ring.removeFirst() }
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
(0..<4).forEach { _ in _ = ring.removeFirst() }
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
(8..<64).forEach { ring.append($0) }
|
||||
XCTAssertTrue(ring.testOnly_verifyInvariantsForNonSlices())
|
||||
|
||||
XCTAssertEqual(Array(4..<64), Array(ring))
|
||||
}
|
||||
|
@ -597,7 +682,7 @@ class CircularBufferTests: XCTestCase {
|
|||
XCTAssertNotNil(dummy1)
|
||||
XCTAssertNotNil(dummy2)
|
||||
|
||||
ring.removeFirst()
|
||||
_ = ring.removeFirst()
|
||||
|
||||
for _ in 2..<8 {
|
||||
ring.append(Dummy())
|
||||
|
@ -653,42 +738,165 @@ class CircularBufferTests: XCTestCase {
|
|||
}
|
||||
|
||||
func testIndexDistance() {
|
||||
let index1 = CircularBuffer<Int>.Index(backingIndex: 0, backingIndexOfHead: 0, backingCount: 4)
|
||||
let index2 = CircularBuffer<Int>.Index(backingIndex: 1, backingIndexOfHead: 0, backingCount: 4)
|
||||
XCTAssertEqual(index1.distance(to: index2), 1)
|
||||
var bufferOfBackingSize4 = CircularBuffer<Int>(initialCapacity: 4)
|
||||
XCTAssertEqual(3, bufferOfBackingSize4.indexBeforeHeadIdx())
|
||||
|
||||
let index3 = CircularBuffer<Int>.Index(backingIndex: 2, backingIndexOfHead: 1, backingCount: 4)
|
||||
let index4 = CircularBuffer<Int>.Index(backingIndex: 0, backingIndexOfHead: 1, backingCount: 4)
|
||||
XCTAssertEqual(index3.distance(to: index4), 2)
|
||||
let index1 = CircularBuffer<Int>.Index(backingIndex: 0, backingIndexOfHead: 0)
|
||||
let index2 = CircularBuffer<Int>.Index(backingIndex: 1, backingIndexOfHead: 0)
|
||||
XCTAssertEqual(bufferOfBackingSize4.distance(from: index1, to: index2), 1)
|
||||
|
||||
let index5 = CircularBuffer<Int>.Index(backingIndex: 0, backingIndexOfHead: 1, backingCount: 4)
|
||||
let index6 = CircularBuffer<Int>.Index(backingIndex: 2, backingIndexOfHead: 1, backingCount: 4)
|
||||
XCTAssertEqual(index5.distance(to: index6), -2)
|
||||
bufferOfBackingSize4.append(1)
|
||||
XCTAssertEqual(1, bufferOfBackingSize4.removeFirst())
|
||||
XCTAssertEqual(1, bufferOfBackingSize4.headBackingIndex)
|
||||
let index3 = CircularBuffer<Int>.Index(backingIndex: 2, backingIndexOfHead: 1)
|
||||
let index4 = CircularBuffer<Int>.Index(backingIndex: 0, backingIndexOfHead: 1)
|
||||
XCTAssertEqual(bufferOfBackingSize4.distance(from: index3, to: index4), 2)
|
||||
|
||||
let index7 = CircularBuffer<Int>.Index(backingIndex: 0, backingIndexOfHead: 3, backingCount: 4)
|
||||
let index8 = CircularBuffer<Int>.Index(backingIndex: 2, backingIndexOfHead: 3, backingCount: 4)
|
||||
XCTAssertEqual(index7.distance(to: index8), 2)
|
||||
let index5 = CircularBuffer<Int>.Index(backingIndex: 0, backingIndexOfHead: 1)
|
||||
let index6 = CircularBuffer<Int>.Index(backingIndex: 2, backingIndexOfHead: 1)
|
||||
XCTAssertEqual(bufferOfBackingSize4.distance(from: index5, to: index6), -2)
|
||||
|
||||
bufferOfBackingSize4.append(2)
|
||||
bufferOfBackingSize4.append(3)
|
||||
XCTAssertEqual(2, bufferOfBackingSize4.removeFirst())
|
||||
XCTAssertEqual(3, bufferOfBackingSize4.removeFirst())
|
||||
XCTAssertEqual(3, bufferOfBackingSize4.headBackingIndex)
|
||||
let index7 = CircularBuffer<Int>.Index(backingIndex: 0, backingIndexOfHead: 3)
|
||||
let index8 = CircularBuffer<Int>.Index(backingIndex: 2, backingIndexOfHead: 3)
|
||||
XCTAssertEqual(bufferOfBackingSize4.distance(from: index7, to: index8), 2)
|
||||
}
|
||||
|
||||
func testIndexAdvancing() {
|
||||
let index1 = CircularBuffer<Int>.Index(backingIndex: 0, backingIndexOfHead: 0, backingCount: 4)
|
||||
let index2 = index1.advanced(by: 1)
|
||||
var bufferOfBackingSize4 = CircularBuffer<Int>(initialCapacity: 4)
|
||||
XCTAssertEqual(3, bufferOfBackingSize4.indexBeforeHeadIdx())
|
||||
|
||||
let index1 = CircularBuffer<Int>.Index(backingIndex: 0, backingIndexOfHead: 0)
|
||||
let index2 = bufferOfBackingSize4.index(after: index1)
|
||||
XCTAssertEqual(index2.backingIndex, 1)
|
||||
XCTAssertEqual(index2.isIndexGEQHeadIndex, true)
|
||||
|
||||
let index3 = CircularBuffer<Int>.Index(backingIndex: 3, backingIndexOfHead: 2, backingCount: 4)
|
||||
let index4 = index3.advanced(by: 1)
|
||||
bufferOfBackingSize4.append(1)
|
||||
bufferOfBackingSize4.append(2)
|
||||
XCTAssertEqual(1, bufferOfBackingSize4.removeFirst())
|
||||
XCTAssertEqual(2, bufferOfBackingSize4.removeFirst())
|
||||
XCTAssertEqual(2, bufferOfBackingSize4.headBackingIndex)
|
||||
|
||||
let index3 = CircularBuffer<Int>.Index(backingIndex: 3, backingIndexOfHead: 2)
|
||||
let index4 = bufferOfBackingSize4.index(after: index3)
|
||||
XCTAssertEqual(index4.backingIndex, 0)
|
||||
XCTAssertEqual(index4.isIndexGEQHeadIndex, false)
|
||||
|
||||
let index5 = CircularBuffer<Int>.Index(backingIndex: 0, backingIndexOfHead: 1, backingCount: 4)
|
||||
let index6 = index5.advanced(by: -1)
|
||||
|
||||
bufferOfBackingSize4.prepend(3)
|
||||
XCTAssertEqual(1, bufferOfBackingSize4.headBackingIndex)
|
||||
let index5 = CircularBuffer<Int>.Index(backingIndex: 0, backingIndexOfHead: 1)
|
||||
let index6 = bufferOfBackingSize4.index(before: index5)
|
||||
XCTAssertEqual(index6.backingIndex, 3)
|
||||
XCTAssertEqual(index6.isIndexGEQHeadIndex, true)
|
||||
|
||||
let index7 = CircularBuffer<Int>.Index(backingIndex: 2, backingIndexOfHead: 1, backingCount: 4)
|
||||
let index8 = index7.advanced(by: -1)
|
||||
let index7 = CircularBuffer<Int>.Index(backingIndex: 2, backingIndexOfHead: 1)
|
||||
let index8 = bufferOfBackingSize4.index(before: index7)
|
||||
XCTAssertEqual(index8.backingIndex, 1)
|
||||
XCTAssertEqual(index8.isIndexGEQHeadIndex, true)
|
||||
}
|
||||
|
||||
func testPopFirst() {
|
||||
var buf = CircularBuffer([1, 2, 3])
|
||||
if let element = buf.popFirst() {
|
||||
XCTAssertEqual(1, element)
|
||||
} else {
|
||||
XCTFail("popFirst didn't find first element")
|
||||
}
|
||||
|
||||
if let element = buf.popFirst() {
|
||||
XCTAssertEqual(2, element)
|
||||
} else {
|
||||
XCTFail("popFirst didn't find second element")
|
||||
}
|
||||
|
||||
if let element = buf.popFirst() {
|
||||
XCTAssertEqual(3, element)
|
||||
} else {
|
||||
XCTFail("popFirst didn't find third element")
|
||||
}
|
||||
|
||||
XCTAssertNil(buf.popFirst())
|
||||
XCTAssertTrue(buf.testOnly_verifyInvariantsForNonSlices())
|
||||
}
|
||||
|
||||
func testSlicing() {
|
||||
var buf = CircularBuffer<Int>()
|
||||
for i in -4..<124 {
|
||||
buf.append(i)
|
||||
}
|
||||
XCTAssertEqual(-4, buf.removeFirst())
|
||||
XCTAssertEqual(-3, buf.removeFirst())
|
||||
XCTAssertEqual(-2, buf.removeFirst())
|
||||
XCTAssertEqual(-1, buf.removeFirst())
|
||||
buf.append(124)
|
||||
buf.append(125)
|
||||
buf.append(126)
|
||||
buf.append(127)
|
||||
|
||||
let buf2: CircularBuffer<Int> = buf[buf.index(buf.startIndex, offsetBy: 100) ..< buf.endIndex]
|
||||
XCTAssertEqual(Array(100..<128), Array(buf2))
|
||||
XCTAssertEqual(Array(100..<128), Array(buf[buf2.startIndex ..< buf2.endIndex]))
|
||||
}
|
||||
|
||||
func testRemoveInMiddle() {
|
||||
var buf = CircularBuffer<Int>(initialCapacity: 8)
|
||||
for i in 0..<7 {
|
||||
buf.append(i)
|
||||
}
|
||||
XCTAssertEqual(0, buf.removeFirst())
|
||||
XCTAssertTrue(buf.testOnly_verifyInvariantsForNonSlices())
|
||||
buf.append(7)
|
||||
XCTAssertEqual(2, buf.remove(at: buf.index(buf.startIndex, offsetBy: 1)))
|
||||
XCTAssertTrue(buf.testOnly_verifyInvariantsForNonSlices())
|
||||
XCTAssertEqual([1, 3, 4, 5, 6, 7], Array(buf))
|
||||
buf.removeAll(keepingCapacity: true)
|
||||
XCTAssertTrue(buf.testOnly_verifyInvariantsForNonSlices())
|
||||
XCTAssertEqual([], Array(buf))
|
||||
}
|
||||
|
||||
func testLotsOfPrepending() {
|
||||
var buf = CircularBuffer<Int>(initialCapacity: 8)
|
||||
|
||||
for i in (0..<128).reversed() {
|
||||
buf.prepend(i)
|
||||
}
|
||||
XCTAssertEqual(Array(0..<128), Array(buf))
|
||||
}
|
||||
|
||||
func testLotsOfInsertAtStart() {
|
||||
var buf = CircularBuffer<Int>(initialCapacity: 8)
|
||||
|
||||
for i in (0..<128).reversed() {
|
||||
buf.insert(i, at: buf.startIndex)
|
||||
}
|
||||
XCTAssertEqual(Array(0..<128), Array(buf))
|
||||
}
|
||||
|
||||
func testLotsOfInsertAtEnd() {
|
||||
var buf = CircularBuffer<Int>(initialCapacity: 8)
|
||||
|
||||
for i in (0..<128) {
|
||||
buf.insert(i, at: buf.endIndex)
|
||||
}
|
||||
XCTAssertEqual(Array(0..<128), Array(buf))
|
||||
}
|
||||
|
||||
func testPopLast() {
|
||||
var buf = CircularBuffer<Int>(initialCapacity: 4)
|
||||
buf.append(1)
|
||||
XCTAssertEqual(1, buf.popLast())
|
||||
XCTAssertNil(buf.popLast())
|
||||
|
||||
buf.append(1)
|
||||
buf.append(2)
|
||||
buf.append(3)
|
||||
XCTAssertEqual(3, buf.popLast())
|
||||
XCTAssertEqual(2, buf.popLast())
|
||||
XCTAssertEqual(1, buf.popLast())
|
||||
XCTAssertNil(buf.popLast())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,11 +37,11 @@ class MarkedCircularBufferTests: XCTestCase {
|
|||
|
||||
XCTAssertTrue(buf.hasMark)
|
||||
XCTAssertEqual(buf.markedElement, 4)
|
||||
XCTAssertEqual(buf.markedElementIndex, buf.startIndex.advanced(by: 3))
|
||||
XCTAssertEqual(buf.markedElementIndex, buf.index(buf.startIndex, offsetBy: 3))
|
||||
|
||||
for i in 0..<3 { XCTAssertFalse(buf.isMarked(index: buf.startIndex.advanced(by: i))) }
|
||||
XCTAssertTrue(buf.isMarked(index: buf.startIndex.advanced(by: 3)))
|
||||
for i in 4..<8 { XCTAssertFalse(buf.isMarked(index: buf.startIndex.advanced(by: i))) }
|
||||
for i in 0..<3 { XCTAssertFalse(buf.isMarked(index: buf.index(buf.startIndex, offsetBy: i))) }
|
||||
XCTAssertTrue(buf.isMarked(index: buf.index(buf.startIndex, offsetBy: 3)))
|
||||
for i in 4..<8 { XCTAssertFalse(buf.isMarked(index: buf.index(buf.startIndex, offsetBy: i))) }
|
||||
}
|
||||
|
||||
func testPassingTheMark() throws {
|
||||
|
@ -55,7 +55,7 @@ class MarkedCircularBufferTests: XCTestCase {
|
|||
XCTAssertEqual(buf.removeFirst(), j)
|
||||
XCTAssertTrue(buf.hasMark)
|
||||
XCTAssertEqual(buf.markedElement, 4)
|
||||
XCTAssertEqual(buf.markedElementIndex, buf.startIndex.advanced(by: 3 - j))
|
||||
XCTAssertEqual(buf.markedElementIndex, buf.index(buf.startIndex, offsetBy: 3 - j))
|
||||
}
|
||||
|
||||
XCTAssertEqual(buf.removeFirst(), 4)
|
||||
|
@ -73,8 +73,8 @@ class MarkedCircularBufferTests: XCTestCase {
|
|||
|
||||
XCTAssertTrue(buf.hasMark)
|
||||
XCTAssertEqual(buf.markedElement, i)
|
||||
XCTAssertEqual(buf.markedElementIndex, buf.startIndex.advanced(by: i - 1))
|
||||
XCTAssertTrue(buf.isMarked(index: buf.startIndex.advanced(by: i - 1)))
|
||||
XCTAssertEqual(buf.markedElementIndex, buf.index(buf.startIndex, offsetBy: i - 1))
|
||||
XCTAssertTrue(buf.isMarked(index: buf.index(buf.startIndex, offsetBy: i - 1)))
|
||||
}
|
||||
}
|
||||
func testIndices() throws {
|
||||
|
@ -82,7 +82,14 @@ class MarkedCircularBufferTests: XCTestCase {
|
|||
for i in 1...4 {
|
||||
buf.append(i)
|
||||
}
|
||||
XCTAssertEqual(buf.indices, buf.startIndex ..< buf.startIndex.advanced(by: 4))
|
||||
|
||||
var allIndices: [MarkedCircularBuffer<Int>.Index] = []
|
||||
var index = buf.startIndex
|
||||
while index != buf.endIndex {
|
||||
allIndices.append(index)
|
||||
index = buf.index(after: index)
|
||||
}
|
||||
XCTAssertEqual(Array(buf.indices), allIndices)
|
||||
}
|
||||
|
||||
func testFirst() throws {
|
||||
|
@ -107,7 +114,7 @@ class MarkedCircularBufferTests: XCTestCase {
|
|||
buf.append(i)
|
||||
}
|
||||
XCTAssertEqual(buf[buf.startIndex], 1)
|
||||
XCTAssertEqual(buf[buf.startIndex.advanced(by: 3)], 4)
|
||||
XCTAssertEqual(buf[buf.index(buf.startIndex, offsetBy: 3)], 4)
|
||||
}
|
||||
|
||||
func testIsEmpty() throws {
|
||||
|
|
Loading…
Reference in New Issue