diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7b5954a7d..e14a8ab8e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -68,10 +68,10 @@ jobs: swiftpm-linux: strategy: matrix: - swift: ["5.2", "5.7"] + swift: ["5.7"] name: SwiftPM Linux - runs-on: ubuntu-18.04 + runs-on: ubuntu-22.04 steps: - name: Setup Swift version uses: swift-actions/setup-swift@v1 diff --git a/CHANGELOG.md b/CHANGELOG.md index e2952609f..e53e5cc5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ 1. Fix minimum deployment target of iOS 11 in CocoaPods 1. Fix CI release git tag push trigger (#869, kudos to @p4checo) +1. Find and remove items from Bag using a binary search to improve performance when the collection gets large. # 7.1.1 1. Bumped deployment target to iOS 11, tvOS 11, watchOS 4, macOS 10.13, per Xcode 14 warnings (#865, kudos to @lickel) diff --git a/Sources/Bag.swift b/Sources/Bag.swift index e38b92b04..163b6d7d2 100644 --- a/Sources/Bag.swift +++ b/Sources/Bag.swift @@ -58,13 +58,45 @@ public struct Bag { /// - token: A token returned from a call to `insert()`. @discardableResult public mutating func remove(using token: Token) -> Element? { - guard let index = indices.first(where: { tokens[$0] == token.value }) else { + // Given that tokens are always added to the end of the array and have a monotonically + // increasing value, this list is always sorted, so we can use a binary search to improve + // performance if this list gets large. + guard let index = binarySearch(tokens, value: token.value) else { return nil } tokens.remove(at: index) return elements.remove(at: index) } + + /// Perform a binary search on a sorted array returning the index of a value. + /// + /// - parameters: + /// - input: The sorted array to search for `value` + /// - value: The value to find in the sorted `input` array + /// + /// - returns: The index of the `value` or `nil` + private func binarySearch(_ input:ContiguousArray, value: UInt64) -> Int? { + var lower = 0 + var upper = input.count - 1 + + while (true) { + let current = (lower + upper)/2 + if(input[current] == value) { + return current + } + + if (lower > upper) { + return nil + } + + if (input[current] > value) { + upper = current - 1 + } else { + lower = current + 1 + } + } + } } extension Bag: RandomAccessCollection {