From bf5df363ea2bf0976f780c540ddaf758166139e6 Mon Sep 17 00:00:00 2001 From: therealbnut Date: Sat, 4 Feb 2017 21:42:52 +1100 Subject: [PATCH 1/6] Add RingBuffer, fixes --- stdlib/public/core/RingBuffer.swift | 257 ++++++++++++++++++++++++++++ stdlib/public/core/Sequence.swift | 40 +---- test/stdlib/PrintRingBuffer.swift | 39 +++++ test/stdlib/RingBuffer.swift | 138 +++++++++++++++ 4 files changed, 443 insertions(+), 31 deletions(-) create mode 100644 stdlib/public/core/RingBuffer.swift create mode 100644 test/stdlib/PrintRingBuffer.swift create mode 100644 test/stdlib/RingBuffer.swift diff --git a/stdlib/public/core/RingBuffer.swift b/stdlib/public/core/RingBuffer.swift new file mode 100644 index 0000000000000..635c433b33d83 --- /dev/null +++ b/stdlib/public/core/RingBuffer.swift @@ -0,0 +1,257 @@ +// +// RingBuffer.swift +// RingBuffer +// +// Created by Andrew Bennett on 29/1/17. +// Copyright © 2017 Andrew Bennett. All rights reserved. +// + +internal struct RingBuffer: + RangeReplaceableCollection, MutableCollection, RandomAccessCollection, + MutableCollectionAlgorithms +{ + private var _bufferCapacity: Int + fileprivate var _buffer: ContiguousArray + fileprivate var _indexOffset: Int + + public typealias Indices = CountableRange + public typealias Iterator = IndexingIterator + public typealias SubSequence = ArraySlice + + private func _checkIndex(_ position: Int) { + precondition(position <= _buffer.count + _bufferCapacity, + "RingBuffer index is out of range") + } + + public init() { + self.init(capacity: 1) + } + + public init(capacity: Int) { + var buffer = ContiguousArray() + buffer.reserveCapacity(capacity) + self.init(buffer, capacity: capacity, offset: 0) + } + init(_ buffer: ContiguousArray, capacity: Int, offset: Int) { + _bufferCapacity = capacity + _buffer = buffer + _indexOffset = offset + } + + public var startIndex: Int { + return 0 + } + public var endIndex: Int { + return _buffer.count + } + public var underestimatedCount: Int { + return _buffer.count + } + public var isFull: Bool { + return _buffer.count == _bufferCapacity + } + public var count: Int { + return _buffer.count + } + public var capacity: Int { + return _bufferCapacity + } + + public mutating func reserveCapacity(_ n: Int) { + rotate(shiftingToStart: _indexOffset) + _buffer.reserveCapacity(Swift.max(n, _bufferCapacity)) + } + + public subscript(bounds: Range) -> SubSequence { + get { + let count = _buffer.count + precondition(bounds.count <= count) + _checkIndex(bounds.lowerBound) + _checkIndex(bounds.upperBound) + let lowerBound = _indexOffset + bounds.lowerBound + let upperBound = _indexOffset + bounds.upperBound + guard lowerBound < count else { + return _buffer[(lowerBound - count) ..< (upperBound - count)] + } + guard upperBound > count else { + return _buffer[lowerBound ..< upperBound] + } + let lhs = _buffer[lowerBound ..< count] + let rhs = _buffer[0 ..< (upperBound - count)] + return SubSequence([lhs, rhs].joined()) + } + set { + replaceSubrange(bounds, with: newValue) + } + } + public subscript(position: Int) -> Element { + get { + _checkIndex(position) + let index = (_indexOffset + position) % _buffer.count + return _buffer[index] + } + set { + _checkIndex(position) + let index = (_indexOffset + position) % _buffer.count + _buffer[index] = newValue + } + } + + public func index(after i: Int) -> Int { + return i + 1 + } + public func index(before i: Int) -> Int { + return i - 1 + } + + public mutating func replaceSubrange( + _ subrange: Range, with newElements: C) where + C : Collection, Element == C.Iterator.Element + { + guard !newElements.isEmpty else { + removeSubrange(subrange) + return + } + guard !isEmpty else { + precondition(subrange.lowerBound == 0) + append(contentsOf: newElements) + return + } + + let count = _buffer.count + let elementsCount = Int(newElements.count.toIntMax()) + let offset = Swift.max(-subrange.count, + Swift.min(elementsCount - subrange.count, + _bufferCapacity - count)) + var elements = newElements.makeIterator() + let insertCount = Swift.max(0, offset) + subrange.count + for _ in 0 ..< Swift.max(0, elementsCount-insertCount) { + _ = elements.next() + } + + if offset <= 0 { + if !subrange.isEmpty { + var index = subrange.lowerBound + while let element = elements.next() { + self[index] = element + index += 1 + if index == subrange.upperBound { + index = subrange.lowerBound + } + } + } + removeSubrange((subrange.upperBound+offset) ..< subrange.upperBound) + } + else { + precondition(count < _bufferCapacity) + precondition(_indexOffset == 0) + for index in (count-offset) ..< count { + _buffer.append(_buffer[Swift.max(index, 0)]) + } + let range = subrange.lowerBound ..< (subrange.upperBound + offset) + var index = range.lowerBound + while let element = elements.next() { + _buffer[index] = element + index += 1 + if index == range.upperBound { + index = range.lowerBound + } + } + } + } + + public mutating func removeSubrange(_ bounds: Range) { + let count = _buffer.count + precondition(bounds.count <= count) + _checkIndex(bounds.lowerBound) + _checkIndex(bounds.upperBound) + guard bounds.lowerBound < bounds.upperBound else { + return + } + guard bounds.count < count else { + removeAll() + return + } + + let newCount = count - bounds.count + + var lowerBound = _indexOffset + bounds.lowerBound + var upperBound = _indexOffset + bounds.upperBound + if lowerBound >= _bufferCapacity { + lowerBound -= _bufferCapacity + upperBound -= _bufferCapacity + } + + if _indexOffset == 0 { + for i in 0 ..< (newCount - bounds.lowerBound) { + _buffer[bounds.lowerBound + i] = _buffer[bounds.upperBound + i] + } + } + else { + for i in bounds.lowerBound ..< newCount { + let from = (_indexOffset + i + bounds.count) % _bufferCapacity + let to = (_indexOffset + i) % _bufferCapacity + _buffer[to] = _buffer[from] + } + rotate(shiftingToStart: _indexOffset) + } + + _buffer.removeSubrange(newCount ..< count) + _indexOffset = 0 + } + + public mutating func removeAll() { + _indexOffset = 0 + _buffer.removeAll(keepingCapacity: true) + } + + public mutating func append(_ newElement: Element) { + if _buffer.count < _bufferCapacity { + _buffer.append(newElement) + } + else { + _buffer[_indexOffset] = newElement + _indexOffset = (_indexOffset + 1) % _bufferCapacity + } + } +} + +extension RingBuffer: ExpressibleByArrayLiteral { + public init(arrayLiteral elements: Element...) { + let buffer = ContiguousArray(elements) + self.init(buffer, capacity: buffer.count, offset: 0) + } +} + +extension RingBuffer: CustomStringConvertible, CustomDebugStringConvertible { + public var description: String { + var output = "[" + if !isEmpty { + output.reserveCapacity(2 + count * 3) + output.append(self.lazy + .map(String.init(describing:)) + .joined(separator: ", ")) + } + output.append("]") + return output + } + + public var debugDescription: String { + var output = "RingBuffer<" + output.append(String(describing: Element.self)) + output.append(",\(capacity)>([") + if !_buffer.isEmpty { + output.reserveCapacity(2 + count * 3) + output.append(_buffer[_indexOffset ..< _buffer.count].lazy + .map(String.init(describing:)) + .joined(separator: ", ")) + output.append("][") + output.append(_buffer[0 ..< _indexOffset].lazy + .map(String.init(describing:)) + .joined(separator: ", ")) + } + output.append("])") + + return output + } +} diff --git a/stdlib/public/core/Sequence.swift b/stdlib/public/core/Sequence.swift index 3d94aa614de01..85d9f82e57047 100644 --- a/stdlib/public/core/Sequence.swift +++ b/stdlib/public/core/Sequence.swift @@ -879,31 +879,14 @@ extension Sequence { public func suffix(_ maxLength: Int) -> AnySequence { _precondition(maxLength >= 0, "Can't take a suffix of negative length from a sequence") if maxLength == 0 { return AnySequence([]) } - // FIXME: Create reusable RingBuffer + // Put incoming elements into a ring buffer to save space. Once all // elements are consumed, reorder the ring buffer into an `Array` // and return it. This saves memory for sequences particularly longer // than `maxLength`. - var ringBuffer: [Iterator.Element] = [] - ringBuffer.reserveCapacity(Swift.min(maxLength, underestimatedCount)) - - var i = ringBuffer.startIndex - - for element in self { - if ringBuffer.count < maxLength { - ringBuffer.append(element) - } else { - ringBuffer[i] = element - i += 1 - i %= maxLength - } - } - - if i != ringBuffer.startIndex { - let s0 = ringBuffer[i..(capacity: capacity) + ringBuffer.append(contentsOf: self) return AnySequence(ringBuffer) } @@ -1206,24 +1189,19 @@ extension Sequence where _precondition(n >= 0, "Can't drop a negative number of elements from a sequence") if n == 0 { return AnySequence(self) } - // FIXME: Create reusable RingBuffer // Put incoming elements from this sequence in a holding tank, a ring buffer // of size <= n. If more elements keep coming in, pull them out of the // holding tank into the result, an `Array`. This saves // `n` * sizeof(Iterator.Element) of memory, because slices keep the entire // memory of an `Array` alive. var result: [Iterator.Element] = [] - var ringBuffer: [Iterator.Element] = [] - var i = ringBuffer.startIndex - + var ringBuffer = RingBuffer(capacity: n) + for element in self { - if ringBuffer.count < n { - ringBuffer.append(element) - } else { - result.append(ringBuffer[i]) - ringBuffer[i] = element - i = ringBuffer.index(after: i) % n + if ringBuffer.isFull, let first = ringBuffer.first { + result.append(first) } + ringBuffer.append(element) } return AnySequence(result) } diff --git a/test/stdlib/PrintRingBuffer.swift b/test/stdlib/PrintRingBuffer.swift new file mode 100644 index 0000000000000..62180908bcb42 --- /dev/null +++ b/test/stdlib/PrintRingBuffer.swift @@ -0,0 +1,39 @@ +// RUN: rm -rf %t && mkdir -p %t +// RUN: %target-build-swift -c -force-single-frontend-invocation -parse-as-library -emit-module -emit-module-path %t/PrintTestTypes.swiftmodule -o %t/PrintTestTypes.o %S/Inputs/PrintTestTypes.swift +// RUN: %target-build-swift %s -Xlinker %t/PrintTestTypes.o -I %t -L %t -o %t/main +// RUN: %target-run %t/main +// REQUIRES: executable_test + +import StdlibUnittest +import PrintTestTypes + +let PrintTests = TestSuite("PrintRingBuffer") + +PrintTests.test("Printable") { + expectPrinted("[]", RingBuffer()) + expectPrinted("[1]", [ 1 ] as RingBuffer) + expectPrinted("[1, 2]", [ 1, 2 ] as RingBuffer) + expectPrinted("[1, 2, 3]", [ 1, 2, 3 ] as RingBuffer) + + expectPrinted("[\"foo\", \"bar\", \"bas\"]", + ["foo", "bar", "bas"] as RingBuffer) + expectDebugPrinted("[\"foo\", \"bar\", \"bas\"]", + ["foo", "bar", "bas"] as RingBuffer) + + expectPrinted("[►1◀︎, ►2◀︎, ►3◀︎]",[StructPrintable(1), + StructPrintable(2), StructPrintable(3)] as RingBuffer) + + expectPrinted("[<10 20 30 40>, <50 60 70 80>]", + [LargeStructPrintable(10, 20, 30, 40), + LargeStructPrintable(50, 60, 70, 80)] as RingBuffer) + + expectPrinted("[►1◀︎]", [StructDebugPrintable(1)] as RingBuffer) + + expectPrinted("[►1◀︎, ►2◀︎, ►3◀︎]", [ClassPrintable(1), + ClassPrintable(2), ClassPrintable(3)] as RingBuffer) + + expectPrinted("[►1◀︎, ►2◀︎, ►3◀︎]", [ClassPrintable(1), + ClassPrintable(2), ClassPrintable(3)] as RingBuffer) +} + +runAllTests() diff --git a/test/stdlib/RingBuffer.swift b/test/stdlib/RingBuffer.swift new file mode 100644 index 0000000000000..5c3c4de24f481 --- /dev/null +++ b/test/stdlib/RingBuffer.swift @@ -0,0 +1,138 @@ +// RUN: %target-run-simple-swift +// REQUIRES: executable_test + +import StdlibUnittest + +let RingBufferTests = TestSuite("RingBuffer") +RingBuffer.test("basic") { + expectEqual(RingBuffer(capacity: 5).capacity, 5) + + expectEqualSequence([1,2,3,4,5] as RingBuffer, make([1,2,3,4,5])) +} + +RingBuffer.test("append") { + expectEqualSequence(make([]) { $0.append(1) }, + make([1])) + + expectEqualSequence(make([0,1,2]) { $0.append(contentsOf: [3,4]) }, + + expectEqualSequence(make([0,1,2]) { $0.append(contentsOf: [3,4,5]) }, + make([1,2,3,4,5])) +} + +RingBuffer.test("remove") { + expectEqualSequence(make([0,1,2]) { $0.removeSubrange(1..<1) }, + make([0,1,2])) + + expectEqualSequence(make([0,1,2,3]) { $0.removeSubrange(0...2) }, + make([3])) + expectEqualSequence(make([0,1],[2,3,4]) { $0.removeSubrange(0...2) }, + make([3,4])) + + expectEqualSequence(make([0,1,2,3]) { $0.removeSubrange(1...2) }, + make([0,3])) + expectEqualSequence(make([0,1],[2,3,4]) { $0.removeSubrange(1...2) }, + make([0,3,4])) + + expectEqualSequence(make([0,1,2,3]) { $0.removeSubrange(2...3) }, + make([0,1])) + expectEqualSequence(make([0,1],[2,3,4]) { $0.removeSubrange(2...4) }, + make([0,1])) + + expectEqualSequence(make([0,1,2,3]) { $0.removeSubrange(0...3) }, + make([])) + expectEqualSequence(make([0,1],[2,3,4]) { $0.removeSubrange(0...4) }, + make([])) + + expectEqualSequence(make([0,1,2,3]) { $0.removeAll() }, + make([])) + expectEqualSequence(make([0,1],[2,3,4]) { $0.removeAll() }, + make([])) +} + +RingBuffer.test("rotate") { + expectEqualSequence(make([0,1,2,3]) { $0.rotate(shiftingToStart: 0) }, + make([0,1,2,3])) + expectEqualSequence(make([0,1],[2,3,4]) { $0.rotate(shiftingToStart: 0) }, + make([0,1,2,3,4])) + + expectEqualSequence(make([0,1,2,3]) { $0.rotate(shiftingToStart: 2) }, + make([2,3,0,1])) + expectEqualSequence(make([0,1],[2,3,4]) { $0.rotate(shiftingToStart: 2) }, + make([2,3,4,0,1])) +} + +RingBuffer.test("replaceSubrange") { + expectEqualSequence(make([]) { $0.replaceSubrange(0..<0, with: [0,1]) }, + make([0,1])) + + expectEqualSequence(make([0,1,2,3]) { $0.replaceSubrange(1...2, with: []) }, + make([0,3])) + expectEqualSequence(make([0,1],[2,3,4]) { $0.replaceSubrange(1...2, + with: []) }, + make([0,3,4])) + + expectEqualSequence(make([0,1,2,3]) { $0.replaceSubrange(1...2, + with: [8,9]) }, + make([0,8,9,3])) + expectEqualSequence(make([0,1],[2,3,4]) { $0.replaceSubrange(1...2, + with: [8,9]) }, + make([0,8,9,3,4])) + + expectEqualSequence(make([0,1,2,3]) { $0.replaceSubrange(1...2, with: [9]) }, + make([0,9,3])) + expectEqualSequence(make([0,1],[2,3,4]) { $0.replaceSubrange(1...2, + with: [9]) }, + make([0,9,3,4])) + + expectEqualSequence(make([0,1,2,3]) { $0.replaceSubrange(1...2, + with: [7,8,9]) }, + make([0,7,8,9,3])) + expectEqualSequence(make([0,1],[2,3,4]) { $0.replaceSubrange(1...2, + with: [7,8,9]) }, + make([0,8,9,3,4])) + + expectEqualSequence(make([0,1,2,3]) { $0.replaceSubrange(1...2, + with: [5,6,7,8,9]) }, + make([0,7,8,9,3])) + expectEqualSequence(make([0,1],[2,3,4]) { $0.replaceSubrange(1...2, + with: [5,6,7,8,9]) }, + make([0,8,9,3,4])) +} + +RingBuffer.test("subrange") { + expectEqualSequence(make([0,1,2,3])[1...2], [1,2]) + expectEqualSequence(make([0,1],[2,3,4])[1...3], [1,2,3]) + expectEqualSequence(make([0,1],[2,3,4])[3...3], [3]) + + expectEqualSequence(make([0,1,2,3]) { $0[1...2].removeAll() }, + make([0,3])) + expectEqualSequence(make([0,1],[2,3,4]) { $0[1...3].removeAll() }, + make([0,4])) +} + +RingBuffer.test("description") { + expectEqualSequence(make([0,1,2,3]).description, + "[0, 1, 2, 3]") + expectEqualSequence(make([0,1],[2,3,4]).description, + "[0, 1, 2, 3, 4]") + + expectEqualSequence(make([0,1,2,3]).debugDescription, + "RingBuffer([0, 1, 2, 3][])") + expectEqualSequence(make([0,1],[2,3,4]).debugDescription, + "RingBuffer([0, 1][2, 3, 4])") +} + +runAllTests() + +func make(_ prefix: [Int], + _ suffix: [Int] = [], + apply: (inout RingBuffer)->Void = { _ in }) -> RingBuffer { + precondition(suffix.count == 0 || (suffix.count + prefix.count) == 5) + var buffer = RingBuffer(ContiguousArray(suffix + prefix), + capacity: 5, + offset: suffix.count) + apply(&buffer) + return buffer +} + From 084944f2581a8a2e675d03ea5cd2a30dc12d4fa2 Mon Sep 17 00:00:00 2001 From: therealbnut Date: Sat, 4 Feb 2017 21:47:39 +1100 Subject: [PATCH 2/6] Move RingBuffer into SwiftExperimental --- .../{public/core => internal/SwiftExperimental}/RingBuffer.swift | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename stdlib/{public/core => internal/SwiftExperimental}/RingBuffer.swift (100%) diff --git a/stdlib/public/core/RingBuffer.swift b/stdlib/internal/SwiftExperimental/RingBuffer.swift similarity index 100% rename from stdlib/public/core/RingBuffer.swift rename to stdlib/internal/SwiftExperimental/RingBuffer.swift From 67b54e9c28f25f3a88b229746ed788c407f3348e Mon Sep 17 00:00:00 2001 From: therealbnut Date: Sun, 5 Feb 2017 09:57:18 +1100 Subject: [PATCH 3/6] copyright --- stdlib/internal/SwiftExperimental/RingBuffer.swift | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/stdlib/internal/SwiftExperimental/RingBuffer.swift b/stdlib/internal/SwiftExperimental/RingBuffer.swift index 635c433b33d83..0b197a2a6d5e5 100644 --- a/stdlib/internal/SwiftExperimental/RingBuffer.swift +++ b/stdlib/internal/SwiftExperimental/RingBuffer.swift @@ -1,10 +1,14 @@ +//===----------------------------------------------------------------------===// // -// RingBuffer.swift -// RingBuffer +// This source file is part of the Swift.org open source project // -// Created by Andrew Bennett on 29/1/17. -// Copyright © 2017 Andrew Bennett. All rights reserved. +// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception // +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// internal struct RingBuffer: RangeReplaceableCollection, MutableCollection, RandomAccessCollection, From 73fead0561f7eb7fc55ac9dea730bb43e98f4995 Mon Sep 17 00:00:00 2001 From: therealbnut Date: Sun, 5 Feb 2017 11:31:14 +1100 Subject: [PATCH 4/6] Cleanup and algorithm explanations --- .../SwiftExperimental/RingBuffer.swift | 287 ++++++++++-------- test/stdlib/RingBuffer.swift | 2 +- 2 files changed, 166 insertions(+), 123 deletions(-) diff --git a/stdlib/internal/SwiftExperimental/RingBuffer.swift b/stdlib/internal/SwiftExperimental/RingBuffer.swift index 0b197a2a6d5e5..44cd5fc6a53b9 100644 --- a/stdlib/internal/SwiftExperimental/RingBuffer.swift +++ b/stdlib/internal/SwiftExperimental/RingBuffer.swift @@ -14,6 +14,22 @@ internal struct RingBuffer: RangeReplaceableCollection, MutableCollection, RandomAccessCollection, MutableCollectionAlgorithms { + // Stores up to _bufferCapacity elements into _buffer the indices start at + // _indexOffset and wrap around. + // + // The notation [0,1][2,3,4] indicates an internal state of: + // _buffer: [2,3,4,0,1] + // _indexOffset: 3 ^ + // _bufferCapacity: 5 + // + // If _buffer.count < _bufferCapacity then this has a few implications: + // * the buffer is not full + // * new elements will be appended to the end of the buffer (for speed) + // * _indexOffset must be zero (0) + // + // Algorithms used in this implementation aim to be O(1) in additional + // memory usage, even at the expense of performance. + private var _bufferCapacity: Int fileprivate var _buffer: ContiguousArray fileprivate var _indexOffset: Int @@ -23,14 +39,14 @@ internal struct RingBuffer: public typealias SubSequence = ArraySlice private func _checkIndex(_ position: Int) { - precondition(position <= _buffer.count + _bufferCapacity, + _precondition(position <= _buffer.count + _bufferCapacity, "RingBuffer index is out of range") } - + public init() { self.init(capacity: 1) } - + public init(capacity: Int) { var buffer = ContiguousArray() buffer.reserveCapacity(capacity) @@ -41,7 +57,7 @@ internal struct RingBuffer: _buffer = buffer _indexOffset = offset } - + public var startIndex: Int { return 0 } @@ -60,16 +76,17 @@ internal struct RingBuffer: public var capacity: Int { return _bufferCapacity } - + public mutating func reserveCapacity(_ n: Int) { rotate(shiftingToStart: _indexOffset) - _buffer.reserveCapacity(Swift.max(n, _bufferCapacity)) + _bufferCapacity = Swift.max(n, _buffer.count) + _buffer.reserveCapacity(_bufferCapacity) } - + public subscript(bounds: Range) -> SubSequence { get { let count = _buffer.count - precondition(bounds.count <= count) + _precondition(bounds.count <= count) _checkIndex(bounds.lowerBound) _checkIndex(bounds.upperBound) let lowerBound = _indexOffset + bounds.lowerBound @@ -100,162 +117,188 @@ internal struct RingBuffer: _buffer[index] = newValue } } - + public func index(after i: Int) -> Int { return i + 1 } public func index(before i: Int) -> Int { return i - 1 } - + public mutating func replaceSubrange( _ subrange: Range, with newElements: C) where C : Collection, Element == C.Iterator.Element { - guard !newElements.isEmpty else { - removeSubrange(subrange) - return - } - guard !isEmpty else { - precondition(subrange.lowerBound == 0) - append(contentsOf: newElements) - return - } - - let count = _buffer.count - let elementsCount = Int(newElements.count.toIntMax()) - let offset = Swift.max(-subrange.count, - Swift.min(elementsCount - subrange.count, - _bufferCapacity - count)) - var elements = newElements.makeIterator() - let insertCount = Swift.max(0, offset) + subrange.count - for _ in 0 ..< Swift.max(0, elementsCount-insertCount) { - _ = elements.next() - } - - if offset <= 0 { - if !subrange.isEmpty { - var index = subrange.lowerBound - while let element = elements.next() { - self[index] = element - index += 1 - if index == subrange.upperBound { - index = subrange.lowerBound - } + guard !newElements.isEmpty else { + removeSubrange(subrange) + return + } + guard !isEmpty else { + _precondition(subrange.lowerBound == 0) + append(contentsOf: newElements) + return + } + + let count = _buffer.count + + // FIXME: Is there a better way to do this + // it's potentially O(n) in newElements, and has an unsafe cast + let newCount = Int(newElements.count.toIntMax()) + // the change in self.count after inserting the elements + let offsMin = -subrange.count, offsMax = _bufferCapacity - count + let offset = Swift.max(offsMin, Swift.min(newCount-subrange.count, offsMax)) + var suffix = newCount.makeIterator() + + // equivalent of suffix(suffixCount), which can't be used as it uses a + // ring buffer, this is also O(1) in memory. + let suffixCount = Swift.max(0, offset) + subrange.count + for _ in 0 ..< Swift.max(0, elementsCount-suffixCount) { + _ = suffix.next() + } + + // If the total number of elements doesn't increase only elements in + // subrange need to be modified. + // We can replace the elements of subrange, then remove excess subrange + // elements. + if offset <= 0 { + if !subrange.isEmpty { + var index = subrange.lowerBound + while let element = suffix.next() { + self[index] = element + index += 1 + // FIXME: This should never happen, due to suffix above + if index == subrange.upperBound { + index = subrange.lowerBound } } - removeSubrange((subrange.upperBound+offset) ..< subrange.upperBound) } - else { - precondition(count < _bufferCapacity) - precondition(_indexOffset == 0) - for index in (count-offset) ..< count { - _buffer.append(_buffer[Swift.max(index, 0)]) - } - let range = subrange.lowerBound ..< (subrange.upperBound + offset) - var index = range.lowerBound - while let element = elements.next() { - _buffer[index] = element - index += 1 - if index == range.upperBound { - index = range.lowerBound - } + removeSubrange((subrange.upperBound+offset) ..< subrange.upperBound) + } + // If the total number of elements increases: + // 1. move elements to the end as needed, or until the buffer is big enough + // 2. insert the new elements into subrange and the new buffer + else { + _precondition(count < _bufferCapacity) + _precondition(_indexOffset == 0) + // FIXME: No need for `uninitializedElement` if elements can be + // uninitialized in a lower-level container. + // Copy elements to the end, until there's room for newElements + let uninitializedElement = _buffer[0] + for index in (count-offset) ..< count { + _buffer.append(index < 0 ? uninitializedElement : _buffer[index]) + } + let range = subrange.lowerBound ..< (subrange.upperBound + offset) + var index = range.lowerBound + while let element = suffix.next() { + _buffer[index] = element + index += 1 + // FIXME: This should never happen, due to suffix above + if index == range.upperBound { + index = range.lowerBound } } + } } - + public mutating func removeSubrange(_ bounds: Range) { - let count = _buffer.count - precondition(bounds.count <= count) - _checkIndex(bounds.lowerBound) - _checkIndex(bounds.upperBound) - guard bounds.lowerBound < bounds.upperBound else { - return - } - guard bounds.count < count else { - removeAll() - return - } - - let newCount = count - bounds.count - - var lowerBound = _indexOffset + bounds.lowerBound - var upperBound = _indexOffset + bounds.upperBound - if lowerBound >= _bufferCapacity { - lowerBound -= _bufferCapacity - upperBound -= _bufferCapacity - } - - if _indexOffset == 0 { - for i in 0 ..< (newCount - bounds.lowerBound) { - _buffer[bounds.lowerBound + i] = _buffer[bounds.upperBound + i] - } + let count = _buffer.count + _precondition(bounds.count <= count) + _checkIndex(bounds.lowerBound) + _checkIndex(bounds.upperBound) + guard bounds.lowerBound < bounds.upperBound else { + return + } + guard bounds.count < count else { + removeAll() + return + } + + let newCount = count - bounds.count + + var lowerBound = _indexOffset + bounds.lowerBound + var upperBound = _indexOffset + bounds.upperBound + if lowerBound >= _bufferCapacity { + lowerBound -= _bufferCapacity + upperBound -= _bufferCapacity + } + + if _indexOffset == 0 { + for i in 0 ..< (newCount - bounds.lowerBound) { + _buffer[bounds.lowerBound + i] = _buffer[bounds.upperBound + i] } - else { - for i in bounds.lowerBound ..< newCount { - let from = (_indexOffset + i + bounds.count) % _bufferCapacity - let to = (_indexOffset + i) % _bufferCapacity - _buffer[to] = _buffer[from] - } - rotate(shiftingToStart: _indexOffset) + } + else { + for i in bounds.lowerBound ..< newCount { + let from = (_indexOffset + i + bounds.count) % _bufferCapacity + let to = (_indexOffset + i) % _bufferCapacity + _buffer[to] = _buffer[from] } - - _buffer.removeSubrange(newCount ..< count) - _indexOffset = 0 + rotate(shiftingToStart: _indexOffset) + } + + _buffer.removeSubrange(newCount ..< count) + _indexOffset = 0 } - + public mutating func removeAll() { - _indexOffset = 0 - _buffer.removeAll(keepingCapacity: true) + _indexOffset = 0 + _buffer.removeAll(keepingCapacity: true) } - + public mutating func append(_ newElement: Element) { - if _buffer.count < _bufferCapacity { - _buffer.append(newElement) - } - else { - _buffer[_indexOffset] = newElement - _indexOffset = (_indexOffset + 1) % _bufferCapacity - } + if _buffer.count < _bufferCapacity { + _buffer.append(newElement) + } + else { + _buffer[_indexOffset] = newElement + _indexOffset = (_indexOffset + 1) % _bufferCapacity + } } } extension RingBuffer: ExpressibleByArrayLiteral { public init(arrayLiteral elements: Element...) { - let buffer = ContiguousArray(elements) - self.init(buffer, capacity: buffer.count, offset: 0) + let buffer = ContiguousArray(elements) + self.init(buffer, capacity: buffer.count, offset: 0) } } extension RingBuffer: CustomStringConvertible, CustomDebugStringConvertible { public var description: String { - var output = "[" - if !isEmpty { - output.reserveCapacity(2 + count * 3) - output.append(self.lazy - .map(String.init(describing:)) - .joined(separator: ", ")) - } - output.append("]") - return output + var output = "[" + if !isEmpty { + output.reserveCapacity(2 + count * 3) + output.append(self.lazy + .map(String.init(describing:)) + .joined(separator: ", ")) + } + output.append("]") + return output } - + public var debugDescription: String { var output = "RingBuffer<" output.append(String(describing: Element.self)) output.append(",\(capacity)>([") if !_buffer.isEmpty { output.reserveCapacity(2 + count * 3) - output.append(_buffer[_indexOffset ..< _buffer.count].lazy - .map(String.init(describing:)) - .joined(separator: ", ")) - output.append("][") - output.append(_buffer[0 ..< _indexOffset].lazy - .map(String.init(describing:)) - .joined(separator: ", ")) + if _buffer.count < _bufferCapacity { + output.append(self.lazy + .map(String.init(describing:)) + .joined(separator: ", ")) + } + else { + output.append(_buffer[_indexOffset ..< _buffer.count].lazy + .map(String.init(describing:)) + .joined(separator: ", ")) + output.append("][") + output.append(_buffer[0 ..< _indexOffset].lazy + .map(String.init(describing:)) + .joined(separator: ", ")) + } } output.append("])") - + return output } } diff --git a/test/stdlib/RingBuffer.swift b/test/stdlib/RingBuffer.swift index 5c3c4de24f481..d78b93232c002 100644 --- a/test/stdlib/RingBuffer.swift +++ b/test/stdlib/RingBuffer.swift @@ -131,7 +131,7 @@ func make(_ prefix: [Int], precondition(suffix.count == 0 || (suffix.count + prefix.count) == 5) var buffer = RingBuffer(ContiguousArray(suffix + prefix), capacity: 5, - offset: suffix.count) + offset: suffix.count) apply(&buffer) return buffer } From f34ad1546bd7f6306645e70dae58faab880092f8 Mon Sep 17 00:00:00 2001 From: therealbnut Date: Sun, 5 Feb 2017 11:41:15 +1100 Subject: [PATCH 5/6] Move into Prototypes --- .../SwiftExperimental => test/Prototypes}/RingBuffer.swift | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {stdlib/internal/SwiftExperimental => test/Prototypes}/RingBuffer.swift (100%) diff --git a/stdlib/internal/SwiftExperimental/RingBuffer.swift b/test/Prototypes/RingBuffer.swift similarity index 100% rename from stdlib/internal/SwiftExperimental/RingBuffer.swift rename to test/Prototypes/RingBuffer.swift From 014777455123dbd9be8358ec38f18f0443f199a8 Mon Sep 17 00:00:00 2001 From: therealbnut Date: Sun, 5 Feb 2017 12:52:41 +1100 Subject: [PATCH 6/6] move into core --- {test/Prototypes => stdlib/public/core}/RingBuffer.swift | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {test/Prototypes => stdlib/public/core}/RingBuffer.swift (100%) diff --git a/test/Prototypes/RingBuffer.swift b/stdlib/public/core/RingBuffer.swift similarity index 100% rename from test/Prototypes/RingBuffer.swift rename to stdlib/public/core/RingBuffer.swift