RingBuffer.swift 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. // Copyright 2021 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. import Foundation
  15. /// A generic circular queue structure.
  16. struct RingBuffer<Element>: Sequence {
  17. /// An array of heartbeats treated as a circular queue and intialized with a fixed capacity.
  18. private var circularQueue: [Element?]
  19. /// The current "tail" and insert point for the `circularQueue`.
  20. private var tailIndex: Int = 0
  21. /// Designated initializer.
  22. /// - Parameter capacity: An `Int` representing the capacity.
  23. init(capacity: Int) {
  24. circularQueue = Array(repeating: nil, count: capacity)
  25. }
  26. /// Pushes an element to the back of the buffer, returning the element (`Element?`) that was overwritten.
  27. /// - Parameter element: The element to push to the back of the buffer.
  28. /// - Returns: The element that was overwritten or `nil` if nothing was overwritten.
  29. /// - Complexity: O(1)
  30. @discardableResult
  31. mutating func push(_ element: Element) -> Element? {
  32. guard circularQueue.count > 0 else {
  33. // Do not push if `circularQueue` is a fixed empty array.
  34. return nil
  35. }
  36. defer {
  37. // Increment index, wrapping around to the start if needed.
  38. tailIndex += 1
  39. tailIndex %= circularQueue.count
  40. }
  41. let replaced = circularQueue[tailIndex]
  42. circularQueue[tailIndex] = element
  43. return replaced
  44. }
  45. /// Pops an element from the back of the buffer, returning the element (`Element?`) that was popped.
  46. /// - Returns: The element that was popped or `nil` if there was no element to pop.
  47. /// - Complexity: O(1)
  48. @discardableResult
  49. mutating func pop() -> Element? {
  50. guard circularQueue.count > 0 else {
  51. // Do not pop if `circularQueue` is a fixed empty array.
  52. return nil
  53. }
  54. // Decrement index, wrapping around to the back if needed.
  55. tailIndex -= 1
  56. if tailIndex < 0 {
  57. tailIndex = circularQueue.count - 1
  58. }
  59. guard let popped = circularQueue[tailIndex] else {
  60. return nil // There is no element to pop.
  61. }
  62. circularQueue[tailIndex] = nil
  63. return popped
  64. }
  65. func makeIterator() -> IndexingIterator<[Element]> {
  66. circularQueue
  67. .compactMap { $0 } // Remove `nil` elements.
  68. .makeIterator()
  69. }
  70. }
  71. // MARK: - Codable
  72. extension RingBuffer: Codable where Element: Codable {}