diff --git a/Sources/PenguinStructures/Deque.swift b/Sources/PenguinStructures/Deque.swift index 61b304f8..416183d6 100644 --- a/Sources/PenguinStructures/Deque.swift +++ b/Sources/PenguinStructures/Deque.swift @@ -28,403 +28,437 @@ public protocol Queue { mutating func push(_ element: Element) } -// MARK: - Deques +// MARK: - Deque -/// A dynamically-sized double-ended queue that allows pushing and popping at both the front and the -/// back. +/// A dynamically-sized queue with efficient additions and removals at both the beginning and the end. +/// +/// Deque's have stable indices, such that pushing and popping elements do not invalidate indices for +/// unaffected elements. public struct Deque { - /// A block of data - private typealias Block = DoubleEndedBuffer + private var spine: Spine +} - /// The elements contained within the data structure. +extension Deque { + /// The number of bits used to encode the per-page element offset. + private static var maxPerBlockElementBits: UInt { 13 } + /// The hard-coded size of a block, in bytes. + private static var blockSize: UInt { 4096 } + /// A mask to extract the offset into a block. + private static var blockOffsetMask: UInt { (1 << maxPerBlockElementBits) - 1 } + /// A mask to extract the page identifier. + private static var blockIDMask: UInt { ~blockOffsetMask } + /// The number of bits used to represent block IDs. + private static var blockIDBitCount: UInt { UInt(UInt.bitWidth) - maxPerBlockElementBits - 1 } + /// Maximum block ID, and can also be used as a bitmask for blockIDs. + internal static var maxBlockID: UInt { (1 << blockIDBitCount) - 1 } + /// The number of elements per block. + private static var elementsPerBlock: UInt { Self.blockSize / UInt(MemoryLayout.stride) } + + /// Prints out sizes for internal data structures. /// - /// Invariant: buff is never empty (it always contains at least one (nested) Block). - private var buff: DoubleEndedBuffer - - /// The number of elements contained within `self`. - public private(set) var count: Int + /// When ensuring Deque works as expected for your platform, this function will print to stdout the sizes of internal + /// data structures. + public static func _printDequeStaticInternalConfiguration() { + print(""" + Deque configuration: + - maxPerBlockElementBits: \(maxPerBlockElementBits) + - blockSize: \(blockSize) bytes + - blockOffsetMask: \(String(blockOffsetMask, radix: 2)) + - blockIDMask: \(String(blockIDMask, radix: 2)) + - blockIDBitCount: \(blockIDBitCount) + - maxBlockID: \(maxBlockID) + - elementsPerBlock: \(elementsPerBlock) + """) + } - /// Creates an empty Deque. - /// - /// - Parameter bufferSize: The capacity (in terms of elements) of the initial Deque. If - /// unspecified, `Deque` uses a heuristic to pick a value, tuned for performance. - public init(initialCapacity: Int? = nil) { - let blockSize: Int - if let capacity = initialCapacity { - blockSize = capacity - } else { - if MemoryLayout.stride < 256 { - // ~4k pages; minus the overheads. - blockSize = (4096 - MemoryLayout.size - 8) / MemoryLayout.stride - } else { - // Store at least 16 elements per block. - blockSize = 16 - } - } - buff = DoubleEndedBuffer(capacity: 16, with: .middle) - buff.pushBack(Block(capacity: blockSize, with: .middle)) - count = 0 + /// Halts the program if the hard-coded configuration values are inconsistent with each other. + private func assertConstantInvariants() { + assert(Self.blockSize == (1 << (Self.maxPerBlockElementBits - 1)), "The block size must be exactly 2^(maxPerBlockElementBits - 1)") + assert(Self.maxBlockID & (Self.maxBlockID + 1) == 0, "The maximum blockID must be one less than a power of 2 for fast masking.") } - /// True iff no values are contained in `self. - public var isEmpty: Bool { count == 0 } + /// A partially- or completely-filled block of elements allocated in page-size increments. + internal typealias Block = UnsafeMutablePointer - private mutating func reallocateBuff() { - if buff.count * 2 < buff.capacity { - // Reallocate to the same size to avoid taking too much memory. - buff.reallocate(newCapacity: buff.capacity, with: .middle) - } else { - buff.reallocate(newCapacity: buff.capacity * 2, with: .middle) - } + // TODO: Make type `Index` exist outside of `Deque` to allow nested collections with intertwined indices (e.g. adjacency lists). + /// A position into the Deque. + public struct Index: Equatable, Hashable, Comparable { + /// Storage for the packed representation of the offset. + internal var storage: UInt + + /// The offset into a block of elements. + internal var blockOffset: UInt { storage & Deque.blockOffsetMask } + /// A stable identifier for a block of storage. + internal var blockID: UInt { ((storage & Deque.blockIDMask) >> maxPerBlockElementBits) & Deque.maxBlockID } + + /// Returns a Boolean value indicating whether the value of the first argument is less than that of the second argument. + public static func < (lhs: Self, rhs: Self) -> Bool { lhs.storage < rhs.storage } // TODO: Is this right? } - /// Add `elem` to the back of `self`. - public mutating func pushBack(_ elem: Element) { - count += 1 - if buff[buff.endIndex - 1].canPushBack { - buff[buff.endIndex - 1].pushBack(elem) - } else { - if buff[buff.endIndex - 1].isEmpty { - // Re-use the previous buffer. - buff[buff.endIndex - 1].pushFront(elem) - } else { - // Allocate a new buffer. - var newBlock = Block(capacity: buff[buff.endIndex - 1].capacity, with: .beginning) - newBlock.pushBack(elem) - if !buff.canPushBack { - reallocateBuff() - } - buff.pushBack(newBlock) - } - } + /// Information on the layout of data within the Deque. + internal struct Metadata { + /// The position of the first element (if non-empty). + var start: Index + /// The position one greater than the last valid index. + var end: Index + /// The offset to use to index into the spine. + var blockOffset: UInt } - /// Removes and returns the element at the back, reducing `self`'s count by one. + /// An ordered collection of pointers to buffers containing Elements. /// - /// - Precondition: !isEmpty - public mutating func popBack() -> Element { - assert(!isEmpty, "Cannot popBack from an empty Deque.") - count -= 1 - let tmp = buff[buff.endIndex - 1].popBack() - if buff[buff.endIndex - 1].isEmpty && buff.count > 1 { - _ = buff.popBack() + /// A Deque is composed of a hierarchy of two buffers: the first is the spine, + internal final class Spine: ManagedBuffer { + + /// The number of elements in `self`. + public var count: Int { + // Note: this calculation works even if there isn't a full page of elements, and has the benefit + // of being fully branchless. + let startBlockElems = Deque.elementsPerBlock - header.start.blockOffset + let endBlockElems = header.end.blockOffset + let middleBlocks = (endBlockOffset - startBlockOffset - 1) * Int(bitPattern: Deque.elementsPerBlock) // Can be negative! + return Int(bitPattern: startBlockElems + endBlockElems) + middleBlocks } - return tmp - } - /// Adds `elem` to the front of `self`. - public mutating func pushFront(_ elem: Element) { - count += 1 - if buff[buff.startIndex].canPushFront { - buff[buff.startIndex].pushFront(elem) - } else { - // Allocate a new buffer. - var newBlock = Block(capacity: buff[buff.startIndex].capacity, with: .end) - newBlock.pushFront(elem) - if !buff.canPushFront { - reallocateBuff() + /// true if `self` contains an element; false otherwise. + public var isEmpty: Bool { header.start == header.end } + + public func index(after i: Index) -> Index { + var next = i + next.storage += 1 + if _slowPath(next.blockOffset == Deque.elementsPerBlock) { + next = Index(blockOffset: 0, blockID: (next.blockID + 1) & Deque.maxBlockID) } - buff.pushFront(newBlock) + return next } - } - /// Removes and returns the element at the front, reducing `self`'s count by one. - /// - /// - Precondition: !isEmpty - public mutating func popFront() -> Element { - precondition(!isEmpty) - count -= 1 - let tmp = buff[buff.startIndex].popFront() - if buff[buff.startIndex].isEmpty && buff.count > 1 { - _ = buff.popFront() + public func index(before i: Index) -> Index { + var next = i + if _slowPath(i.blockOffset == 0) { + next = Index(blockOffset: Deque.elementsPerBlock - 1, blockID: (i.blockID - 1) & Deque.maxBlockID) + } else { + next.storage -= 1 + } + return next } - return tmp - } -} -extension Deque: Queue { - public mutating func pop() -> Element? { - if isEmpty { - return nil + /// The index into `self`'s element pointer for the start block. + internal var startBlockOffset: Int { + spineOffsetForIndex(header.start) } - return popFront() - } - - public mutating func push(_ element: Element) { - pushBack(element) - } -} -extension Deque: HierarchicalCollection { - - public struct Cursor: Equatable, Comparable { - let outerIndex: Int - let innerIndex: Int + /// The index into `self`'s element pointer for the end block. + internal var endBlockOffset: Int { + spineOffsetForIndex(header.end) + } - public static func < (lhs: Self, rhs: Self) -> Bool { - if lhs.outerIndex < rhs.outerIndex { return true } - if lhs.outerIndex > rhs.outerIndex { return false } - return lhs.innerIndex < rhs.innerIndex + /// Returns the offset into the elements of `self` corresponding to `index`'s `blockID`. + internal func spineOffsetForIndex(_ index: Index) -> Int { + let base = index.blockID + header.blockOffset + return Int(bitPattern: base & Deque.maxBlockID) // Use bitPattern to avoid extra branches / traps. } - } - /// Call `fn` for each element in the collection until `fn` returns false. - /// - /// - Parameter start: Start iterating at elements corresponding to this index. If nil, starts at - /// the beginning of the collection. - /// - Returns: a cursor into the data structure corresponding to the first element that returns - /// false. - @discardableResult - public func forEachWhile( - startingAt start: Cursor?, - _ fn: (Element) throws -> Bool - ) rethrows -> Cursor? { - let startPoint = - start - ?? Cursor( - outerIndex: buff.startIndex, - innerIndex: buff[buff.startIndex].startIndex) - - /// Start with potential partial first buffer. - for i in startPoint.innerIndex.. Spine { + let buff = Spine.create(minimumCapacity: Int(blockCount)) { buff in + buff.withUnsafeMutablePointerToElements { + $0.initialize(repeating: nil, count: buff.capacity) + } + return Metadata(start: Index(storage: 0), end: Index(storage: 0), blockOffset: blockCount / 2) } + return buff as! Spine } - // Nested loops for remainder of data structure. - for outer in (startPoint.outerIndex + 1).. Spine { + let oldCapacity = capacity + let oldMetadata = header + let oldStartOffset = spineOffsetForIndex(header.start) + let oldEndOffset = spineOffsetForIndex(header.end) + let s = Spine.create(minimumCapacity: oldCapacity) { _ in oldMetadata } + withUnsafeMutablePointerToElements { old in + s.withUnsafeMutablePointerToElements { new in + if oldStartOffset > 0 { + new.initialize(repeating: nil, count: oldStartOffset) + } + if oldStartOffset == oldEndOffset { + // Initialize only the relevant portions of memory. + if old[oldStartOffset] != nil { + let newPage = Block.allocate(capacity: Int(bitPattern: Deque.elementsPerBlock)) + let blockOffset = oldMetadata.start.blockOffset + let blockCount = oldMetadata.end.blockOffset - blockOffset + (newPage + blockOffset).initialize(from: old[oldStartOffset]! + blockOffset, count: blockCount) + new[oldStartOffset] = newPage + } else { new[oldStartOffset] = nil } + } else { + // Copy both the first & last page. + let newStartPage = Block.allocate(capacity: Int(bitPattern: Deque.elementsPerBlock)) + let startBlockOffset = oldMetadata.start.blockOffset + (newStartPage + startBlockOffset).initialize( + from: old[oldStartOffset]! + startBlockOffset, + count: Deque.elementsPerBlock - startBlockOffset) + new[oldStartOffset] = newStartPage + if old[oldEndOffset] != nil { + let newEndPage = Block.allocate(capacity: Int(bitPattern: Deque.elementsPerBlock)) + newEndPage.initialize(from: old[oldEndOffset], count: oldMetadata.end.blockOffset) + new[oldEndOffset] = newEndPage + } else { new[oldEndOffset] = nil } + // Copy all intermediate pages (if any). + if oldStartOffset + 1 < oldEndOffset - 1 { + for i in (oldStartOffset + 1)...(oldEndOffset - 1) { + // Make a copy of the page. + let newBlock = Block.allocate(capacity: Int(bitPattern: Deque.elementsPerBlock)) + newBlock.initialize(from: old[i]!, count: Int(bitPattern: Deque.elementsPerBlock)) + new[i] = newBlock + } + } + } + if oldEndOffset + 1 < s.capacity { + (new + oldEndOffset + 1).initialize(repeating: nil, count: s.capacity - oldEndOffset - 1) + } } } + return s as! Spine } - return nil } } -/// A fixed-size, contiguous collection allowing additions and removals at either end (space -/// permitting). -/// -/// Beware: unbalanced pushes/pops to either the front or the back will result in the effective -/// working size to be diminished. If you would like this to be managed for you automatically, -/// please use a `Deque`. -/// -/// - SeeAlso: `Deque` -public struct DoubleEndedBuffer { - private var buff: ManagedBuffer - - /// Allocate with a given capacity and insertion `initialPolicy`. - /// - /// - Parameter capacity: The capacity of the buffer. - /// - Parameter initialPolicy: The policy for where initial values should be inserted into the - /// buffer. Note: asymmetric pushes/pops to front/back will cause the portion of the consumed - /// buffer to drift. If you need management to occur automatically, please use a Deque. - public init(capacity: Int, with initialPolicy: DoubleEndedAllocationPolicy) { - assert(capacity > 3) - buff = DoubleEndedBufferImpl.create(minimumCapacity: capacity) { buff in - switch initialPolicy { - case .beginning: - return DoubleEndedHeader(start: 0, end: 0) - case .middle: - let approxMiddle = buff.capacity / 2 - return DoubleEndedHeader(start: approxMiddle, end: approxMiddle) - case .end: - return DoubleEndedHeader(start: buff.capacity, end: buff.capacity) +extension Deque { + /// Ensures a particular block is allocated and returns the block pointer. + internal mutating func ensureAllocatedBlock(at i: Index) -> Block { + var spineOffset = spine.spineOffsetForIndex(i) + // First, check to ensure the spineOffset is not out of bounds. + if _slowPath(spineOffset < 0 || spineOffset >= spine.capacity) { + // Must modify the spine. + let startOffset = spine.spineOffsetForIndex(startIndex) + let endOffset = spine.spineOffsetForIndex(endIndex) + let occupiedSlots = endOffset - startOffset + if occupiedSlots + 1 < spine.capacity { + // Re-center and avoid reallocating a larger spine. + let newStartOffset = (spine.capacity - occupiedSlots) / 2 + let newEndOffset = newStartOffset + occupiedSlots + assert(newStartOffset > 0) + assert(newEndOffset < spine.capacity) + assert(newStartOffset != startOffset) + spine.withUnsafeMutablePointerToElements { elems in + let newStart = elems + newStartOffset + let oldStart = elems + startOffset + newStart.assign(from: UnsafePointer(oldStart), count: occupiedSlots) + if newStartOffset > startOffset { + oldStart.assign(repeating: nil, count: newStartOffset - startOffset) + } else { + let newEnd = elems + newEndOffset + newEnd.assign(repeating: nil, count: endOffset - newEndOffset) + } + } + let newOffset = Int(spine.header.blockOffset) + (newStartOffset - startOffset) + spine.header.blockOffset = UInt(newOffset) & Deque.maxBlockID + } else { + // Must allocate a new, larger spine; no need to copy the data blocks. + let newSpine = Spine.create(minimumCapacity: 2 * spine.capacity) { newSpine in + // Re-center while we're at it. + let newStartOffset = (newSpine.capacity - occupiedSlots) / 2 + spine.withUnsafeMutablePointerToElements { oldBuff in + newSpine.withUnsafeMutablePointerToElements { newBuff in + newBuff.initialize(repeating: nil, count: newStartOffset) + let newStart = newBuff + newStartOffset + newStart.initialize(from: UnsafePointer(oldBuff + startOffset), count: occupiedSlots) + (newStart + occupiedSlots).initialize(repeating: nil, count: newSpine.capacity - occupiedSlots - newStartOffset) + } + } + let newBlockOffset = Int(startIndex.blockID) + newStartOffset + return Metadata(start: startIndex, end: endIndex, blockOffset: UInt(bitPattern: newBlockOffset) & Deque.maxBlockID) + } + spine = newSpine as! Spine } + spineOffset = spine.spineOffsetForIndex(i) + } + // Next check if the block is allocated. + return spine.withUnsafeMutablePointerToElements { elems in + if _fastPath(elems[spineOffset] != nil) { return elems[spineOffset]! } + // Check to see if we have an empty allocated block available to reuse. + let beforeStart = spine.spineOffsetForIndex(startIndex) - 1 + if beforeStart >= 0 && elems[beforeStart] != nil { + let reuseBlock = elems[beforeStart]! + elems[spineOffset] = reuseBlock + elems[beforeStart] = nil + return reuseBlock + } + let afterEnd = spine.spineOffsetForIndex(endIndex) + 1 + if afterEnd < spine.capacity && elems[afterEnd] != nil { + let reuseBlock = elems[afterEnd]! + elems[afterEnd] = nil + elems[spineOffset] = reuseBlock + return reuseBlock + } + let newBlock = Block.allocate(capacity: Int(bitPattern: Deque.elementsPerBlock)) + elems[spineOffset] = newBlock + return newBlock } } - /// True if no elements are contained within the data structure, false otherwise. - public var isEmpty: Bool { - buff.header.start == buff.header.end + /// Indicates a given block should be considered empty and optionally deallocated. + /// + /// In order to avoid degenerate performance cases of allocating and deallocating a block of memory + /// (e.g. repeatedly pushing and popping a single element right at the page boundary), a block may be + /// lazily deallocated. In order to take advantage of this performance optimization, blocks should be + /// allocated with `ensureAllocatedBlock` and deallocated with `markEmptyBlock`. + internal mutating func markEmptyBlock(at spineOffset: Int) { + // TODO: Implement this optimization. } - /// Returns the number of elements contained within `self`. - public var count: Int { - buff.header.end - buff.header.start + /// Ensure that `self` holds uniquely-referenced storage, copying its memory if necessary. + internal mutating func ensureUniqueStorage() { + if !isKnownUniquelyReferenced(&spine) { + spine = spine.deepClone() + } } +} - /// Returns the capacity of `self`. - public var capacity: Int { - buff.capacity +extension Deque: Collection, BidirectionalCollection { + public var startIndex: Index { spine.header.start } + public var endIndex: Index { spine.header.end } + public var count: Int { spine.count } + public var isEmpty: Bool { spine.isEmpty } + public func index(after i: Index) -> Index { spine.index(after: i) } + public func index(before i: Index) -> Index { spine.index(before: i) } + + public subscript(i: Index) -> Element { + // TODO: Ensure `i` is a valid index! + let spineOffset = spine.spineOffsetForIndex(i) + return spine.withUnsafeMutablePointerToElements { $0[spineOffset]![Int(i.blockOffset)] } } +} - /// True iff there is available space at the beginning of the buffer. - public var canPushFront: Bool { - buff.header.start != 0 - } +// TODO: Conform Deque to RandomAccessCollection, MutableCollection, and RangeReplaceableCollection. - /// True iff there is available space at the end of the buffer. - public var canPushBack: Bool { - buff.header.end < buff.capacity +extension Deque { + + /// Creates an empty Deque. + public init() { + self.spine = Spine.createEmpty() } - /// Add elem to the back of the buffer. - /// - /// - Precondition: `canPushBack`. - public mutating func pushBack(_ elem: T) { - precondition(canPushBack, "Cannot pushBack!") - ensureBuffIsUniquelyReferenced() - buff.withUnsafeMutablePointerToElements { buffP in - let offset = buffP.advanced(by: buff.header.end) - offset.initialize(to: elem) + /// Creates an instance with the same elements as `contents`. + public init(_ contents: Contents) where Contents.Element == Element { + self.init() + for e in contents { + pushBack(e) } - buff.header.end += 1 + } + + /// Add `elem` to the back of `self`. + public mutating func pushBack(_ elem: Element) { + ensureUniqueStorage() + let i = endIndex + let block = ensureAllocatedBlock(at: i) + let position = block + Int(bitPattern: i.blockOffset) + position.initialize(to: elem) + spine.header.end = spine.index(after: i) } /// Removes and returns the element at the back, reducing `self`'s count by one. /// /// - Precondition: !isEmpty - public mutating func popBack() -> T { - precondition(!isEmpty, "Cannot popBack from empty buffer!") - ensureBuffIsUniquelyReferenced() - buff.header.end -= 1 - return buff.withUnsafeMutablePointerToElements { buffP in - buffP.advanced(by: buff.header.end).move() + public mutating func popBack() -> Element { + ensureUniqueStorage() + let i = spine.index(before: endIndex) + let offset = spine.spineOffsetForIndex(i) + let block = spine.withUnsafeMutablePointerToElements { $0[offset]! } + let position = block + Int(bitPattern: i.blockOffset) + let returnValue = position.move() + spine.header.end = i + if _slowPath(i.blockOffset == 0) { + markEmptyBlock(at: offset) } + return returnValue } - /// Adds elem to the front of the buffer. - /// - /// - Precondition: `canPushFront`. - public mutating func pushFront(_ elem: T) { - precondition(canPushFront, "Cannot pushFront!") - ensureBuffIsUniquelyReferenced() - buff.header.start -= 1 - buff.withUnsafeMutablePointerToElements { buffP in - let offset = buffP.advanced(by: buff.header.start) - offset.initialize(to: elem) - } + /// Adds `elem` to the front of `self`. + public mutating func pushFront(_ elem: Element) { + ensureUniqueStorage() + let i = spine.index(before: startIndex) + let block = ensureAllocatedBlock(at: i) + let position = block + Int(bitPattern: i.blockOffset) + position.initialize(to: elem) + spine.header.start = i } /// Removes and returns the element at the front, reducing `self`'s count by one. - public mutating func popFront() -> T { - precondition(!isEmpty, "Cannot popFront from empty buffer!") - ensureBuffIsUniquelyReferenced() - buff.header.start += 1 - return buff.withUnsafeMutablePointerToElements { buffP in - buffP.advanced(by: buff.header.start - 1).move() + /// + /// - Precondition: !isEmpty + public mutating func popFront() -> Element { + ensureUniqueStorage() + let offset = spine.spineOffsetForIndex(startIndex) + let block = spine.withUnsafeMutablePointerToElements { $0[offset]! } + let position = block + Int(bitPattern: startIndex.blockOffset) + let returnValue = position.move() + let newStart = spine.index(after: startIndex) + if _slowPath(spine.spineOffsetForIndex(newStart) != offset) { + markEmptyBlock(at: offset) } + spine.header.start = newStart + return returnValue } +} - /// Makes a copy of the backing buffer if it's not uniquely referenced; does nothing otherwise. - private mutating func ensureBuffIsUniquelyReferenced() { - if !isKnownUniquelyReferenced(&buff) { - // make a copy of the backing store. - let copy = DoubleEndedBufferImpl.create(minimumCapacity: buff.capacity) { copy in - return buff.header - } - copy.withUnsafeMutablePointerToElements { copyP in - buff.withUnsafeMutablePointerToElements { buffP in - let copyOffset = copyP.advanced(by: buff.header.start) - let buffOffset = UnsafePointer(buffP).advanced(by: buff.header.start) - copyOffset.initialize(from: buffOffset, count: buff.header.end - buff.header.start) - } - } - self.buff = copy +extension Deque: Queue { + public mutating func pop() -> Element? { + if isEmpty { + return nil } + return popFront() } - /// Explicitly reallocate the backing buffer. - public mutating func reallocate( - newCapacity: Int, - with initialPolicy: DoubleEndedAllocationPolicy - ) { - assert(newCapacity >= count) - let newHeader: DoubleEndedHeader - switch initialPolicy { - case .beginning: - newHeader = DoubleEndedHeader(start: 0, end: count) - case .middle: - let newStart = (newCapacity - count) / 2 - newHeader = DoubleEndedHeader(start: newStart, end: newStart + count) - case .end: - newHeader = DoubleEndedHeader(start: newCapacity - count, end: newCapacity) - } - let copy = DoubleEndedBufferImpl.create(minimumCapacity: newCapacity) { copy in - newHeader - } - if !isKnownUniquelyReferenced(&buff) { - // Don't touch existing one; must make a copy of buffer contents. - copy.withUnsafeMutablePointerToElements { copyP in - buff.withUnsafeMutablePointerToElements { buffP in - let copyOffset = copyP.advanced(by: copy.header.start) - let buffOffset = UnsafePointer(buffP).advanced(by: buff.header.start) - copyOffset.initialize(from: buffOffset, count: count) - } - } - } else { - // Move values out of existing buffer into new buffer. - copy.withUnsafeMutablePointerToElements { copyP in - buff.withUnsafeMutablePointerToElements { buffP in - let copyOffset = copyP.advanced(by: copy.header.start) - let buffOffset = buffP.advanced(by: buff.header.start) - copyOffset.moveInitialize(from: buffOffset, count: count) - } - } - buff.header.end = buff.header.start // don't deinitialize uninitialized memory. - } - self.buff = copy + public mutating func push(_ element: Element) { + pushBack(element) } } -extension DoubleEndedBuffer: Collection { - public var startIndex: Int { buff.header.start } - public var endIndex: Int { buff.header.end } - public func index(after: Int) -> Int { after + 1 } - public subscript(index: Int) -> T { - get { - assert(index >= buff.header.start) - assert(index < buff.header.end) - return buff.withUnsafeMutablePointerToElements { $0[index] } - } - _modify { - assert(index >= buff.header.start) - assert(index < buff.header.end) - ensureBuffIsUniquelyReferenced() - var tmp = buff.withUnsafeMutablePointerToElements { $0.advanced(by: index).move() } - // Ensure we re-initialize the memory! - defer { - buff.withUnsafeMutablePointerToElements { - $0.advanced(by: index).initialize(to: tmp) - } - } - yield &tmp - } +extension Deque.Index { + /// Creates an Index from a given blockOffset and blockID. + /// + /// - Precondition: blockOffset is a valid block offset. + internal init(blockOffset: UInt, blockID: UInt) { + precondition((blockOffset & Deque.blockOffsetMask) == blockOffset, "blockOffset \(blockOffset) is not a valid block offset") + storage = (blockID << Deque.maxPerBlockElementBits) | blockOffset } } -/// Describes where the initial insertions into a buffer should go. -/// -/// - SeeAlso: `DoubleEndedBuffer` -public enum DoubleEndedAllocationPolicy { - /// Begin allocating elements at the beginning of the buffer. - case beginning - /// Begin allocating in the middle of the buffer. - case middle - /// Begin allocating at the end of the buffer. - case end -} - -private struct DoubleEndedHeader { - // TODO: use smaller `Int`s to save memory. - /// The first index with valid data. - var start: Int - /// The index one after the last index with valid data. - var end: Int +extension Deque.Index: CustomStringConvertible { + public var description: String { + "Index(blockID: \(blockID), blockOffset: \(blockOffset))" + } } -private class DoubleEndedBufferImpl: ManagedBuffer { - deinit { - if header.end != header.start { - withUnsafeMutablePointerToElements { elems in - let base = elems.advanced(by: header.start) - base.deinitialize(count: header.end - header.start) +extension Deque.Spine: CustomStringConvertible { + public var description: String { + func formatBlock(_ block: Deque.Block?) -> String { + if let block = block { + return "\(block)" + } else { + return "nil" } } + var body = "" + withUnsafeMutablePointerToElements { buff in + for i in 0.. Self { + return lhs + Int(rhs) + } + + fileprivate func initialize(from: UnsafeMutablePointer?, count: UInt) { + initialize(from: UnsafePointer(from!), count: Int(bitPattern: count)) } } diff --git a/Sources/PenguinStructures/DoubleEndedBuffer.swift b/Sources/PenguinStructures/DoubleEndedBuffer.swift new file mode 100644 index 00000000..9a067553 --- /dev/null +++ b/Sources/PenguinStructures/DoubleEndedBuffer.swift @@ -0,0 +1,237 @@ +// Copyright 2020 Penguin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// A fixed-size, contiguous collection allowing additions and removals at either end (space +/// permitting). +/// +/// Beware: unbalanced pushes/pops to either the front or the back will result in the effective +/// working size to be diminished. If you would like this to be managed for you automatically, +/// please use a `Deque`. +/// +/// - SeeAlso: `Deque` +public struct DoubleEndedBuffer { + private var buff: ManagedBuffer + + /// Allocate with a given capacity and insertion `initialPolicy`. + /// + /// - Parameter capacity: The capacity of the buffer. + /// - Parameter initialPolicy: The policy for where initial values should be inserted into the + /// buffer. Note: asymmetric pushes/pops to front/back will cause the portion of the consumed + /// buffer to drift. If you need management to occur automatically, please use a Deque. + public init(capacity: Int, with initialPolicy: DoubleEndedAllocationPolicy) { + assert(capacity > 3) + buff = DoubleEndedBufferImpl.create(minimumCapacity: capacity) { buff in + switch initialPolicy { + case .beginning: + return DoubleEndedHeader(start: 0, end: 0) + case .middle: + let approxMiddle = buff.capacity / 2 + return DoubleEndedHeader(start: approxMiddle, end: approxMiddle) + case .end: + return DoubleEndedHeader(start: buff.capacity, end: buff.capacity) + } + } + } + + /// True if no elements are contained within the data structure, false otherwise. + public var isEmpty: Bool { + buff.header.start == buff.header.end + } + + /// Returns the number of elements contained within `self`. + public var count: Int { + buff.header.end - buff.header.start + } + + /// Returns the capacity of `self`. + public var capacity: Int { + buff.capacity + } + + /// True iff there is available space at the beginning of the buffer. + public var canPushFront: Bool { + buff.header.start != 0 + } + + /// True iff there is available space at the end of the buffer. + public var canPushBack: Bool { + buff.header.end < buff.capacity + } + + /// Add elem to the back of the buffer. + /// + /// - Precondition: `canPushBack`. + public mutating func pushBack(_ elem: T) { + precondition(canPushBack, "Cannot pushBack!") + ensureBuffIsUniquelyReferenced() + buff.withUnsafeMutablePointerToElements { buffP in + let offset = buffP.advanced(by: buff.header.end) + offset.initialize(to: elem) + } + buff.header.end += 1 + } + + /// Removes and returns the element at the back, reducing `self`'s count by one. + /// + /// - Precondition: !isEmpty + public mutating func popBack() -> T { + precondition(!isEmpty, "Cannot popBack from empty buffer!") + ensureBuffIsUniquelyReferenced() + buff.header.end -= 1 + return buff.withUnsafeMutablePointerToElements { buffP in + buffP.advanced(by: buff.header.end).move() + } + } + + /// Adds elem to the front of the buffer. + /// + /// - Precondition: `canPushFront`. + public mutating func pushFront(_ elem: T) { + precondition(canPushFront, "Cannot pushFront!") + ensureBuffIsUniquelyReferenced() + buff.header.start -= 1 + buff.withUnsafeMutablePointerToElements { buffP in + let offset = buffP.advanced(by: buff.header.start) + offset.initialize(to: elem) + } + } + + /// Removes and returns the element at the front, reducing `self`'s count by one. + public mutating func popFront() -> T { + precondition(!isEmpty, "Cannot popFront from empty buffer!") + ensureBuffIsUniquelyReferenced() + buff.header.start += 1 + return buff.withUnsafeMutablePointerToElements { buffP in + buffP.advanced(by: buff.header.start - 1).move() + } + } + + /// Makes a copy of the backing buffer if it's not uniquely referenced; does nothing otherwise. + private mutating func ensureBuffIsUniquelyReferenced() { + if !isKnownUniquelyReferenced(&buff) { + // make a copy of the backing store. + let copy = DoubleEndedBufferImpl.create(minimumCapacity: buff.capacity) { copy in + return buff.header + } + copy.withUnsafeMutablePointerToElements { copyP in + buff.withUnsafeMutablePointerToElements { buffP in + let copyOffset = copyP.advanced(by: buff.header.start) + let buffOffset = UnsafePointer(buffP).advanced(by: buff.header.start) + copyOffset.initialize(from: buffOffset, count: buff.header.end - buff.header.start) + } + } + self.buff = copy + } + } + + /// Explicitly reallocate the backing buffer. + public mutating func reallocate( + newCapacity: Int, + with initialPolicy: DoubleEndedAllocationPolicy + ) { + assert(newCapacity >= count) + let newHeader: DoubleEndedHeader + switch initialPolicy { + case .beginning: + newHeader = DoubleEndedHeader(start: 0, end: count) + case .middle: + let newStart = (newCapacity - count) / 2 + newHeader = DoubleEndedHeader(start: newStart, end: newStart + count) + case .end: + newHeader = DoubleEndedHeader(start: newCapacity - count, end: newCapacity) + } + let copy = DoubleEndedBufferImpl.create(minimumCapacity: newCapacity) { copy in + newHeader + } + if !isKnownUniquelyReferenced(&buff) { + // Don't touch existing one; must make a copy of buffer contents. + copy.withUnsafeMutablePointerToElements { copyP in + buff.withUnsafeMutablePointerToElements { buffP in + let copyOffset = copyP.advanced(by: copy.header.start) + let buffOffset = UnsafePointer(buffP).advanced(by: buff.header.start) + copyOffset.initialize(from: buffOffset, count: count) + } + } + } else { + // Move values out of existing buffer into new buffer. + copy.withUnsafeMutablePointerToElements { copyP in + buff.withUnsafeMutablePointerToElements { buffP in + let copyOffset = copyP.advanced(by: copy.header.start) + let buffOffset = buffP.advanced(by: buff.header.start) + copyOffset.moveInitialize(from: buffOffset, count: count) + } + } + buff.header.end = buff.header.start // don't deinitialize uninitialized memory. + } + self.buff = copy + } +} + +extension DoubleEndedBuffer: Collection { + public var startIndex: Int { buff.header.start } + public var endIndex: Int { buff.header.end } + public func index(after: Int) -> Int { after + 1 } + + public subscript(index: Int) -> T { + get { + assert(index >= buff.header.start) + assert(index < buff.header.end) + return buff.withUnsafeMutablePointerToElements { $0[index] } + } + _modify { + assert(index >= buff.header.start) + assert(index < buff.header.end) + ensureBuffIsUniquelyReferenced() + var tmp = buff.withUnsafeMutablePointerToElements { $0.advanced(by: index).move() } + // Ensure we re-initialize the memory! + defer { + buff.withUnsafeMutablePointerToElements { + $0.advanced(by: index).initialize(to: tmp) + } + } + yield &tmp + } + } +} + +/// Describes where the initial insertions into a buffer should go. +/// +/// - SeeAlso: `DoubleEndedBuffer` +public enum DoubleEndedAllocationPolicy { + /// Begin allocating elements at the beginning of the buffer. + case beginning + /// Begin allocating in the middle of the buffer. + case middle + /// Begin allocating at the end of the buffer. + case end +} + +internal struct DoubleEndedHeader { + // TODO: use smaller `Int`s to save memory. + /// The first index with valid data. + var start: Int + /// The index one after the last index with valid data. + var end: Int +} + +private class DoubleEndedBufferImpl: ManagedBuffer { + deinit { + if header.end != header.start { + withUnsafeMutablePointerToElements { elems in + let base = elems.advanced(by: header.start) + base.deinitialize(count: header.end - header.start) + } + } + } +} diff --git a/Tests/PenguinStructuresTests/DequeInternalTests.swift b/Tests/PenguinStructuresTests/DequeInternalTests.swift new file mode 100644 index 00000000..f94979a6 --- /dev/null +++ b/Tests/PenguinStructuresTests/DequeInternalTests.swift @@ -0,0 +1,36 @@ +// Copyright 2020 Penguin Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@testable import PenguinStructures +import XCTest + +final class DequeInternalTests: XCTestCase { + func testIndexComputedProperties() { + typealias Index = Deque.Index + + XCTAssertEqual(0, Index(storage: 0).blockOffset) + XCTAssertEqual(0, Index(storage: 0).blockID) + + XCTAssertEqual(5, Index(blockOffset: 5, blockID: 23).blockOffset) + XCTAssertEqual(23, Index(blockOffset: 5, blockID: 23).blockID) + + XCTAssertEqual( + UInt(bitPattern: Int(-105)) & Deque.maxBlockID, + Index(blockOffset: 3096, blockID: UInt(bitPattern: Int(-105))).blockID) + } + + static var allTests = [ + ("testIndexComputedProperties", testIndexComputedProperties), + ] +} diff --git a/Tests/PenguinStructuresTests/DequeTests.swift b/Tests/PenguinStructuresTests/DequeTests.swift index 730c45c8..029810ea 100644 --- a/Tests/PenguinStructuresTests/DequeTests.swift +++ b/Tests/PenguinStructuresTests/DequeTests.swift @@ -59,7 +59,6 @@ final class DequeTests: XCTestCase { for i in 2048..<(2048 * 8) { d.pushBack(i) } - XCTAssertEqual(2048 * 8, d.count) XCTAssertEqual(2048, t1.count) XCTAssertEqual(0, t0.count) @@ -73,17 +72,14 @@ final class DequeTests: XCTestCase { } } - func testHierarchicalCollection() { - var d = Deque() - for i in 0..<2048 { - d.pushBack(i) - } - XCTAssertEqual(Array(0..<2048), d.flatten()) + func testCollectionSemantics() { + let d = Deque(0..<100) + d.checkBidirectionalCollectionSemantics(expecting: 0..<100) } static var allTests = [ ("testSimple", testSimple), ("testValueSemantics", testValueSemantics), - ("testHierarchicalCollection", testHierarchicalCollection), + ("testCollectionSemantics", testCollectionSemantics), ] } diff --git a/Tests/PenguinStructuresTests/XCTestManifests.swift b/Tests/PenguinStructuresTests/XCTestManifests.swift index 3527f7c7..fd636463 100644 --- a/Tests/PenguinStructuresTests/XCTestManifests.swift +++ b/Tests/PenguinStructuresTests/XCTestManifests.swift @@ -26,6 +26,7 @@ import XCTest testCase(CollectionAlgorithmTests.allTests), testCase(ConcatenationTests.allTests), testCase(DequeTests.allTests), + testCase(DequeInternalTests.allTests), testCase(DoubleEndedBufferTests.allTests), testCase(EitherCollectionTests.allTests), testCase(EitherTests.allTests),