Created
June 3, 2018 20:24
-
-
Save nat-n/982fffceb7f838db121bf848e7b37d24 to your computer and use it in GitHub Desktop.
CircularBuffer implementation in Swift 4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public struct CircularBuffer<T>: Collection, CustomStringConvertible { | |
private var cursor: Int = 0 | |
private var contents: [T] = [] | |
let capacity: Int | |
public init(capacity: Int) { | |
self.capacity = capacity | |
} | |
public subscript(i: Int) -> T { | |
get { | |
if i >= capacity || i < 0 { | |
fatalError("Index out of range") | |
} | |
return contents[(i + cursor) % capacity] | |
} | |
set(newValue) { | |
if i >= capacity || i < 0 { | |
fatalError("Index out of range") | |
} | |
contents[(i + cursor) % capacity] = newValue | |
} | |
} | |
func blaaaa() { | |
for i in self { | |
print(i) | |
} | |
} | |
public var count: Int { | |
return contents.count | |
} | |
public var last: T { | |
return contents[contents.count - 1] | |
} | |
private var slice: ArraySlice<T> { | |
return contents[cursor ..< contents.count] + contents[0 ..< cursor] | |
} | |
public var array: [T] { | |
return Array(slice) | |
} | |
public func makeIterator() -> CircularBufferIterator<T> { | |
return CircularBufferIterator<T>(self) | |
} | |
public mutating func append(_ newElement: T) { | |
if contents.count < capacity { | |
contents.append(newElement) | |
return | |
} | |
contents[cursor] = newElement | |
cursor = (cursor + 1) % capacity | |
} | |
public mutating func append(contentsOf newElements: [T]) { | |
let newElemsSlice = newElements[ | |
Swift.max(0, newElements.count - capacity) ..< newElements.count] | |
if newElements.count >= capacity { | |
print("__") | |
contents = Array(newElemsSlice) | |
cursor = 0 | |
return | |
} | |
let countBackFromCursor = cursor - (capacity - newElements.count) | |
contents = Array( | |
(countBackFromCursor < 0 | |
? contents[Swift.max(cursor, (contents.count + countBackFromCursor)) ..< contents.count] | |
: []) + | |
contents[Swift.max(0, countBackFromCursor) ..< cursor] + | |
newElemsSlice | |
) | |
cursor = Swift.max(0, cursor - contents.count - newElements.count) | |
} | |
public mutating func prepend(_ newElement: T) { | |
if contents.count < capacity { | |
contents.insert(newElement, at: 0) | |
return | |
} | |
cursor = (cursor == 0 ? capacity : cursor) - 1 | |
contents[cursor] = newElement | |
} | |
@discardableResult | |
public mutating func remove(at: Int) -> T { | |
if at >= capacity || at < 0 { | |
fatalError("Index out of range") | |
} | |
let index = (at + cursor) % capacity | |
let result = contents[index] | |
contents = Array(contents[0..<index] + contents[index + 1 ..< contents.count]) | |
cursor = 0 | |
return result | |
} | |
public mutating func removeAll() { | |
contents = [] | |
cursor = 0 | |
} | |
public var startIndex: Int { return 0 } | |
public var endIndex: Int { return contents.count } | |
public func index(after i: Index) -> Index { | |
return contents.index(after: i) | |
} | |
public func joined(separator: String = ", ") -> String { | |
return slice.map({ String(describing: $0) }).joined(separator: separator) | |
} | |
public var description: String { | |
return "CircularBuffer<\(String(describing: T.self))>(capacity: \(capacity), [\(joined(separator: ", "))])" | |
} | |
// public var debugDescription: String { return "Me!" } | |
} | |
public struct CircularBufferIterator<T>: IteratorProtocol { | |
var circularBuffer: CircularBuffer<T> | |
var index: Int | |
public init(_ circularBuffer: CircularBuffer<T>) { | |
self.circularBuffer = circularBuffer | |
index = -1 | |
} | |
public mutating func next() -> T? { | |
index += 1 | |
if index >= self.circularBuffer.count { | |
return nil | |
} | |
return self.circularBuffer[index] | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import XCTest | |
class CircularBufferTests: XCTestCase { | |
var cb: CircularBuffer<Int>! | |
override func setUp() { | |
super.setUp() | |
cb = CircularBuffer<Int>(capacity: 5) | |
} | |
func testAppendMoreThanSize() { | |
cb.append(0) | |
XCTAssertEqual(cb.array, [0]) | |
cb.append(1) | |
XCTAssertEqual(cb.array, [0, 1]) | |
cb.append(2) | |
XCTAssertEqual(cb.array, [0, 1, 2]) | |
cb.append(3) | |
XCTAssertEqual(cb.array, [0, 1, 2, 3]) | |
cb.append(4) | |
XCTAssertEqual(cb.array, [0, 1, 2, 3, 4]) | |
cb.append(5) | |
XCTAssertEqual(cb.array, [1, 2, 3, 4, 5]) | |
cb.append(6) | |
XCTAssertEqual(cb.array, [2, 3, 4, 5, 6]) | |
} | |
func testPrependMoreThanSize() { | |
cb.prepend(0) | |
XCTAssertEqual(cb.array, [0]) | |
cb.prepend(1) | |
XCTAssertEqual(cb.array, [1, 0]) | |
cb.prepend(2) | |
XCTAssertEqual(cb.array, [2, 1, 0]) | |
cb.prepend(3) | |
XCTAssertEqual(cb.array, [3, 2, 1, 0]) | |
cb.prepend(4) | |
XCTAssertEqual(cb.array, [4, 3, 2, 1, 0]) | |
cb.prepend(5) | |
XCTAssertEqual(cb.array, [5, 4, 3, 2, 1]) | |
cb.prepend(6) | |
XCTAssertEqual(cb.array, [6, 5, 4, 3, 2]) | |
} | |
func testAlternatingPrependAndAppend() { | |
cb.append(0) | |
XCTAssertEqual(cb.array, [0]) | |
cb.prepend(1) | |
XCTAssertEqual(cb.array, [1, 0]) | |
cb.append(2) | |
XCTAssertEqual(cb.array, [1, 0, 2]) | |
cb.prepend(3) | |
XCTAssertEqual(cb.array, [3, 1, 0, 2]) | |
cb.append(4) | |
XCTAssertEqual(cb.array, [3, 1, 0, 2, 4]) | |
cb.prepend(5) | |
XCTAssertEqual(cb.array, [5, 3, 1, 0, 2]) | |
cb.append(6) | |
XCTAssertEqual(cb.array, [3, 1, 0, 2, 6]) | |
} | |
func testAppendMany() { | |
cb.append(contentsOf: [0, 1, 2]) | |
XCTAssertEqual(cb.array, [0, 1, 2]) | |
cb.append(contentsOf: [3, 4, 5]) | |
XCTAssertEqual(cb.array, [1, 2, 3, 4, 5]) | |
cb.append(contentsOf: [6, 7, 8]) | |
XCTAssertEqual(cb.array, [4, 5, 6, 7, 8]) | |
cb.append(contentsOf: [10, 11, 12, 13, 14, 15, 16, 17]) | |
XCTAssertEqual(cb.array, [13, 14, 15, 16, 17]) | |
} | |
func testRemoveAt() { | |
cb.append(contentsOf: [10, 11, 12, 13, 14]) | |
cb.remove(at: 2) | |
XCTAssertEqual(cb.array, [10, 11, 13, 14]) | |
cb.append(contentsOf: [15, 16]) | |
cb.remove(at: 2) | |
XCTAssertEqual(cb.array, [11, 13, 15, 16]) | |
} | |
func testRemoveAll() { | |
cb.append(contentsOf: [10, 11, 12, 13, 14]) | |
XCTAssertEqual(cb.array, [10, 11, 12, 13, 14]) | |
XCTAssertEqual(cb.count, 5) | |
XCTAssertEqual(cb.isEmpty, false) | |
cb.removeAll() | |
XCTAssertEqual(cb.array, []) | |
XCTAssertEqual(cb.count, 0) | |
XCTAssertEqual(cb.isEmpty, true) | |
} | |
func testJoined() { | |
cb.append(contentsOf: [10, 11, 12, 13, 14]) | |
XCTAssertEqual(cb.joined(), "10, 11, 12, 13, 14") | |
cb.append(1) | |
cb.append(2) | |
XCTAssertEqual(cb.joined(), "12, 13, 14, 1, 2") | |
} | |
func testIndexing() { | |
cb.append(contentsOf: [10, 11, 12, 13, 14]) | |
XCTAssertEqual(cb[0], 10) | |
cb.prepend(0) | |
XCTAssertEqual(cb[0], 0) | |
cb.prepend(1) | |
XCTAssertEqual(cb[0], 1) | |
cb.append(1) | |
XCTAssertEqual(cb[0], 0) | |
cb.append(2) | |
XCTAssertEqual(cb.first, 10) | |
XCTAssertEqual(cb.last, 2) | |
} | |
func testIteration() { | |
cb.append(contentsOf: [10, 11, 12, 13, 14]) | |
cb.append(15) | |
var expectedContent = [11, 12, 13, 14, 15].makeIterator() | |
cb.forEach { i in XCTAssertEqual(i, expectedContent.next()) } | |
} | |
func testRangeSubscripting() { | |
cb.append(contentsOf: [10, 11, 12, 13, 14]) | |
cb.append(15) | |
XCTAssertEqual(Array(cb[1..<3]), [12, 13]) | |
XCTAssertEqual(Array(cb[0...]), [11, 12, 13, 14, 15]) | |
XCTAssertEqual(Array(cb[1...3]), [12, 13, 14]) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment