From e1c75578e516e1613d60550b4e57407a3f91509f Mon Sep 17 00:00:00 2001
From: Finn Voorhees <finnvoorhees@gmail.com>
Date: Sun, 3 Sep 2023 13:09:41 -0400
Subject: [PATCH 1/9] Switch back to Swift 5.9

I would like to be able to use the latest syntax features, not sure if
<5.9 would be useful to others.
---
 Package.swift | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Package.swift b/Package.swift
index 9665fac..861b36e 100644
--- a/Package.swift
+++ b/Package.swift
@@ -1,4 +1,4 @@
-// swift-tools-version: 5.6
+// swift-tools-version: 5.9
 
 import PackageDescription
 

From af7db24bcd54b42cc980f94c05a6b682d4f70774 Mon Sep 17 00:00:00 2001
From: Finn Voorhees <finnvoorhees@gmail.com>
Date: Mon, 4 Sep 2023 11:32:26 -0400
Subject: [PATCH 2/9] Add github actions to build and test (#5)

---
 .github/workflows/build.yml | 30 ++++++++++++++++++++++++++++++
 .github/workflows/test.yml  | 21 +++++++++++++++++++++
 2 files changed, 51 insertions(+)
 create mode 100644 .github/workflows/build.yml
 create mode 100644 .github/workflows/test.yml

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..1cefe4a
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,30 @@
+name: Build
+
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+    branches:
+      - main
+
+env:
+  DEVELOPER_DIR: /Applications/Xcode_15.0.app/Contents/Developer
+
+jobs:
+  build:
+    name: Build
+    runs-on: macOS-13
+    strategy:
+      matrix:
+        destination:
+          - "generic/platform=ios"
+          - "platform=macOS"
+#          - "generic/platform=xros"
+          - "generic/platform=tvos"
+          - "generic/platform=watchos"
+
+    steps:
+      - uses: actions/checkout@v3
+      - name: Build platform ${{ matrix.destination }}
+        run: set -o pipefail && xcodebuild build -scheme FindFaster -destination "${{ matrix.destination }}" | xcpretty
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..051ce74
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,21 @@
+name: Test
+
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+    branches:
+      - main
+
+env:
+  DEVELOPER_DIR: /Applications/Xcode_15.0.app/Contents/Developer
+
+jobs:
+  test:
+    name: Test
+    runs-on: macOS-13
+    steps:
+      - uses: actions/checkout@v3
+      - name: Test
+        run: set -o pipefail && xcodebuild test -scheme FindFaster -destination "platform=macOS" | xcpretty

From 9860899470d47fd80bbe70cbf6dc08a09dac38f0 Mon Sep 17 00:00:00 2001
From: Finn Voorhees <finnvoorhees@gmail.com>
Date: Mon, 4 Sep 2023 11:40:44 -0400
Subject: [PATCH 3/9] Add sync and closure based variants (#3)

* Add sync and closure based variants

* Add documentation
---
 README.md                                     |  38 ++++-
 .../BidirectionalCollection+fastSearch.swift  | 148 +++++++++++-------
 Tests/FindFasterTests/FindFasterTests.swift   |  51 ++++--
 3 files changed, 166 insertions(+), 71 deletions(-)

diff --git a/README.md b/README.md
index 696b962..0f03ea8 100644
--- a/README.md
+++ b/README.md
@@ -2,13 +2,19 @@
 
 [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FFinnvoor%2FFindFaster%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/Finnvoor/FindFaster) [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FFinnvoor%2FFindFaster%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/Finnvoor/FindFaster)
 
+Fast asynchronous swift collection search using the [_Boyer–Moore string-search algorithm_](https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string-search_algorithm).  `fastSearch` can be used with any `BidirectionalCollection` where `Element` is `Equatable` and `Hashable`, and is especially useful for searching large amounts of data or long strings and displaying the results as they come in.
+
+FindFaster is used for find and replace in [HextEdit](https://apps.apple.com/app/apple-store/id1557247094?pt=120542042&ct=github&mt=8), a fast and native macOS hex editor.
+
+## Usage
+### Async 
 ```swift
 import FindFaster
 
 let text = "Lorem ipsum dolor sit amet"
 let search = "or"
 
-for await index in text.fastSearch(for: search) {
+for await index in text.fastSearchStream(for: search) {
     print("Found match at: \(index)")
 }
 
@@ -17,6 +23,32 @@ for await index in text.fastSearch(for: search) {
 //  Found match at: 15
 ```
 
-Fast asynchronous swift collection search using the [_Boyer–Moore string-search algorithm_](https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string-search_algorithm).  `fastSearch` can be used with any `BidirectionalCollection` where `Element` is `Equatable` and `Hashable`, and is especially useful for searching large amounts of data or long strings and displaying the results as they come in.
+### Sync
+```swift
+import FindFaster
 
-FindFaster is used for find and replace in [HextEdit](https://apps.apple.com/app/apple-store/id1557247094?pt=120542042&ct=github&mt=8), a fast and native macOS hex editor.
+let text = "Lorem ipsum dolor sit amet"
+let search = "or"
+
+let results = text.fastSearch(for: search)
+print("Results: \(results)")
+
+// Prints:
+//  Results: [1, 15]
+```
+
+### Closure-based
+```swift
+import FindFaster
+
+let text = "Lorem ipsum dolor sit amet"
+let search = "or"
+
+text.fastSearch(for: search) { index in
+    print("Found match at: \(index)")
+}
+
+// Prints:
+//  Found match at: 1
+//  Found match at: 15
+```
diff --git a/Sources/FindFaster/BidirectionalCollection+fastSearch.swift b/Sources/FindFaster/BidirectionalCollection+fastSearch.swift
index 528c171..f2f3990 100644
--- a/Sources/FindFaster/BidirectionalCollection+fastSearch.swift
+++ b/Sources/FindFaster/BidirectionalCollection+fastSearch.swift
@@ -1,79 +1,111 @@
 import Foundation
 
-extension BidirectionalCollection where Element: Equatable, Element: Hashable {
-    public func fastSearch(for element: Self.Element) -> AsyncStream<Index> {
-        singleElementSearch(for: element)
+public extension BidirectionalCollection where Element: Equatable, Element: Hashable {
+    /// Returns an `AsyncStream` delivering indices where the specified value appears in the collection.
+    /// - Parameter element: An element to search for in the collection.
+    /// - Returns: An `AsyncStream` delivering indices where `element` is found.
+    func fastSearchStream(for element: Element) -> AsyncStream<Index> {
+        fastSearchStream(for: [element])
     }
 
-    public func fastSearch(for searchSequence: some Collection<Element>) -> AsyncStream<Index> {
-        switch searchSequence.count {
-        case 0: AsyncStream { $0.finish() }
-        case 1: singleElementSearch(for: searchSequence[searchSequence.startIndex])
-        default: multiElementSearch(for: searchSequence)
-        }
-    }
-
-    private func singleElementSearch(for element: Element) -> AsyncStream<Index> {
+    /// Returns an `AsyncStream` delivering indices where the specified sequence appears in the collection.
+    /// - Parameter searchSequence: A sequence of elements to search for in the collection.
+    /// - Returns: An `AsyncStream` delivering indices where `searchSequence` is found.
+    func fastSearchStream(for searchSequence: some Collection<Element>) -> AsyncStream<Index> {
         AsyncStream { continuation in
             let task = Task {
-                var currentIndex = startIndex
-                while currentIndex < endIndex, !Task.isCancelled {
-                    if self[currentIndex] == element {
-                        continuation.yield(currentIndex)
-                    }
-                    currentIndex = index(after: currentIndex)
+                fastSearch(for: searchSequence) { index in
+                    continuation.yield(index)
                 }
                 continuation.finish()
             }
-            continuation.onTermination = { _ in
-                task.cancel()
+            continuation.onTermination = { _ in task.cancel() }
+        }
+    }
+
+    /// Returns the indices where the specified value appears in the collection.
+    /// - Parameters:
+    ///   - element: An element to search for in the collection.
+    ///   - onSearchResult: An optional closure that is called when a matching index is found.
+    /// - Returns: The indices where `element` is found. If `element` is not found in the collection, returns an empty array.
+    @discardableResult func fastSearch(
+        for element: Element,
+        onSearchResult: ((Index) -> Void)? = nil
+    ) -> [Index] {
+        fastSearch(for: [element], onSearchResult: onSearchResult)
+    }
+
+    /// Returns the indices where the specified sequence appears in the collection.
+    /// - Parameters:
+    ///   - searchSequence: A sequence of elements to search for in the collection.
+    ///   - onSearchResult: An optional closure that is called when a matching index is found.
+    /// - Returns: The indices where `searchSequence` is found. If `searchSequence` is not found in the collection, returns an empty array.
+    @discardableResult func fastSearch(
+        for searchSequence: some Collection<Element>,
+        onSearchResult: ((Index) -> Void)? = nil
+    ) -> [Index] {
+        switch searchSequence.count {
+        case 0: []
+        case 1: naiveSingleElementSearch(for: searchSequence.first!, onSearchResult: onSearchResult)
+        default: boyerMooreMultiElementSearch(for: searchSequence, onSearchResult: onSearchResult)
+        }
+    }
+}
+
+private extension BidirectionalCollection where Element: Equatable, Element: Hashable {
+    @discardableResult func naiveSingleElementSearch(
+        for element: Element,
+        onSearchResult: ((Index) -> Void)? = nil
+    ) -> [Index] {
+        var indices: [Index] = []
+        var currentIndex = startIndex
+        while currentIndex < endIndex, !Task.isCancelled {
+            if self[currentIndex] == element {
+                indices.append(currentIndex)
+                onSearchResult?(currentIndex)
             }
+            currentIndex = index(after: currentIndex)
         }
+        return indices
     }
 
     /// Boyer–Moore algorithm
-    private func multiElementSearch(for searchSequence: some Collection<Element>) -> AsyncStream<Index> {
-        AsyncStream { continuation in
-            guard !searchSequence.isEmpty, searchSequence.count <= count else {
-                continuation.finish()
-                return
-            }
+    @discardableResult func boyerMooreMultiElementSearch(
+        for searchSequence: some Collection<Element>,
+        onSearchResult: ((Index) -> Void)? = nil
+    ) -> [Index] {
+        guard searchSequence.count <= count else { return [] }
 
-            let task = Task {
-                let skipTable: [Element: Int] = searchSequence
-                    .enumerated()
-                    .reduce(into: [:]) { $0[$1.element] = searchSequence.count - $1.offset - 1 }
+        var indices: [Index] = []
+        let skipTable: [Element: Int] = searchSequence
+            .enumerated()
+            .reduce(into: [:]) { $0[$1.element] = searchSequence.count - $1.offset - 1 }
 
-                var currentIndex = index(startIndex, offsetBy: searchSequence.count - 1)
-                while currentIndex < endIndex, !Task.isCancelled {
-                    let skip = skipTable[self[currentIndex]] ?? searchSequence.count
-                    if skip == 0 {
-                        let lowerBound = index(currentIndex, offsetBy: -searchSequence.count + 1)
-                        let upperBound = index(currentIndex, offsetBy: 1)
-                        if self[lowerBound..<upperBound].elementsEqual(searchSequence) {
-                            continuation.yield(lowerBound)
-                        }
-                        guard let nextIndex = index(
-                            currentIndex,
-                            offsetBy: Swift.max(skip, 1),
-                            limitedBy: endIndex
-                        ) else { break }
-                        currentIndex = nextIndex
-                    } else {
-                        guard let nextIndex = index(
-                            currentIndex,
-                            offsetBy: skip,
-                            limitedBy: endIndex
-                        ) else { break }
-                        currentIndex = nextIndex
-                    }
+        var currentIndex = index(startIndex, offsetBy: searchSequence.count - 1)
+        while currentIndex < endIndex, !Task.isCancelled {
+            let skip = skipTable[self[currentIndex]] ?? searchSequence.count
+            if skip == 0 {
+                let lowerBound = index(currentIndex, offsetBy: -searchSequence.count + 1)
+                let upperBound = index(currentIndex, offsetBy: 1)
+                if self[lowerBound..<upperBound].elementsEqual(searchSequence) {
+                    onSearchResult?(lowerBound)
+                    indices.append(lowerBound)
                 }
-                continuation.finish()
-            }
-
-            continuation.onTermination = { _ in
-                task.cancel()
+                guard let nextIndex = index(
+                    currentIndex,
+                    offsetBy: Swift.max(skip, 1),
+                    limitedBy: endIndex
+                ) else { break }
+                currentIndex = nextIndex
+            } else {
+                guard let nextIndex = index(
+                    currentIndex,
+                    offsetBy: skip,
+                    limitedBy: endIndex
+                ) else { break }
+                currentIndex = nextIndex
             }
         }
+        return indices
     }
 }
diff --git a/Tests/FindFasterTests/FindFasterTests.swift b/Tests/FindFasterTests/FindFasterTests.swift
index c8eeffd..4e80ad3 100644
--- a/Tests/FindFasterTests/FindFasterTests.swift
+++ b/Tests/FindFasterTests/FindFasterTests.swift
@@ -2,25 +2,56 @@
 import XCTest
 
 final class FindFasterTests: XCTestCase {
-    func testSingleElementSearch() async {
-        let collection = (0..<100)
-        let randomElement = collection.randomElement()!
+    let collection1 = (0..<100)
+    let collection2 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris vel lacus in risus finibus semper vel eu magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Quisque pulvinar gravida varius. Nulla facilisi. Nullam dignissim egestas pellentesque. Morbi nulla sem, porta eu feugiat vitae, faucibus sit amet tellus. Curabitur nunc ligula, scelerisque id rhoncus ac, facilisis quis odio. Pellentesque egestas luctus rutrum. Nam auctor, ligula auctor suscipit elementum, nunc nunc dignissim nibh, eget sollicitudin diam ligula eget ligula. Etiam sed est fermentum, fermentum leo et, vestibulum nisi. Vivamus vestibulum quam sed mattis volutpat. Suspendisse a mi gravida, placerat metus vel, euismod quam. Sed vehicula velit a justo porta eleifend. Sed fringilla auctor nisi elementum lobortis."
 
+    func testSingleElementSearchSync() {
+        let search = collection1.randomElement()!
+        let results = collection1.fastSearch(for: search)
+        XCTAssertEqual(results, [search])
+    }
+
+    func testMultiElementSearchSync() {
+        let search = "et"
+        let results = collection2
+            .fastSearch(for: search)
+            .map { collection2.distance(from: collection2.startIndex, to: $0) }
+        XCTAssertEqual(results, [24, 35, 142, 179, 343, 535, 565, 615, 717])
+    }
+
+    func testSingleElementSearchClosure() {
+        let search = collection1.randomElement()!
         var results: [Int] = []
-        for await index in collection.fastSearch(for: randomElement) {
+        collection1.fastSearch(for: search) { index in
             results.append(index)
         }
-        XCTAssertEqual(results, [randomElement])
+        XCTAssertEqual(results, [search])
     }
 
-    func testMultiElementSearch() async {
-        let collection = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris vel lacus in risus finibus semper vel eu magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Quisque pulvinar gravida varius. Nulla facilisi. Nullam dignissim egestas pellentesque. Morbi nulla sem, porta eu feugiat vitae, faucibus sit amet tellus. Curabitur nunc ligula, scelerisque id rhoncus ac, facilisis quis odio. Pellentesque egestas luctus rutrum. Nam auctor, ligula auctor suscipit elementum, nunc nunc dignissim nibh, eget sollicitudin diam ligula eget ligula. Etiam sed est fermentum, fermentum leo et, vestibulum nisi. Vivamus vestibulum quam sed mattis volutpat. Suspendisse a mi gravida, placerat metus vel, euismod quam. Sed vehicula velit a justo porta eleifend. Sed fringilla auctor nisi elementum lobortis."
+    func testMultiElementSearchClosure() {
         let search = "et"
+        var results: [Int] = []
+        collection2.fastSearch(for: search) { index in
+            results.append(self.collection2.distance(from: self.collection2.startIndex, to: index))
+        }
+        XCTAssertEqual(results, [24, 35, 142, 179, 343, 535, 565, 615, 717])
+    }
+
+    func testSingleElementSearchAsync() async {
+        let search = collection1.randomElement()!
+        var results: [Int] = []
+        for await index in collection1.fastSearchStream(for: search) {
+            results.append(index)
+        }
+        XCTAssertEqual(results, [search])
+    }
 
+    func testMultiElementSearchAsync() async {
+        let search = "et"
         var results: [Int] = []
-        for await index in collection.fastSearch(for: search) {
-            results.append(collection.distance(from: collection.startIndex, to: index))
-            XCTAssertEqual(String(collection[index..<collection.index(index, offsetBy: search.count)]), search)
+        for await index in collection2.fastSearchStream(for: search) {
+            results.append(collection2.distance(from: collection2.startIndex, to: index))
+            XCTAssertEqual(String(collection2[index..<collection2.index(index, offsetBy: search.count)]), search)
         }
         XCTAssertEqual(results, [24, 35, 142, 179, 343, 535, 565, 615, 717])
     }

From 1418f8fc1448462ce260be35e350ecf9cdeca3c9 Mon Sep 17 00:00:00 2001
From: Finn Voorhees <finnvoorhees@gmail.com>
Date: Mon, 4 Sep 2023 11:46:34 -0400
Subject: [PATCH 4/9] Add .spi.yml (#4)

---
 .spi.yml | 4 ++++
 1 file changed, 4 insertions(+)
 create mode 100644 .spi.yml

diff --git a/.spi.yml b/.spi.yml
new file mode 100644
index 0000000..e0cf872
--- /dev/null
+++ b/.spi.yml
@@ -0,0 +1,4 @@
+version: 1
+builder:
+  configs:
+    - documentation_targets: [FindFaster]

From 48108cf064f5d942f3f120a08c78bc93f87fc02d Mon Sep 17 00:00:00 2001
From: Finn Voorhees <finnvoorhees@gmail.com>
Date: Mon, 4 Sep 2023 15:12:37 -0400
Subject: [PATCH 5/9] Update README.md (#6)

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 0f03ea8..3141d65 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
 
 [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FFinnvoor%2FFindFaster%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/Finnvoor/FindFaster) [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FFinnvoor%2FFindFaster%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/Finnvoor/FindFaster)
 
-Fast asynchronous swift collection search using the [_Boyer–Moore string-search algorithm_](https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string-search_algorithm).  `fastSearch` can be used with any `BidirectionalCollection` where `Element` is `Equatable` and `Hashable`, and is especially useful for searching large amounts of data or long strings and displaying the results as they come in.
+Fast asynchronous swift collection search using the [_Boyer–Moore string-search algorithm_](https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string-search_algorithm).  `fastSearch` can be used with any `BidirectionalCollection` where `Element` is `Hashable`, and is especially useful for searching large amounts of data or long strings and displaying the results as they come in.
 
 FindFaster is used for find and replace in [HextEdit](https://apps.apple.com/app/apple-store/id1557247094?pt=120542042&ct=github&mt=8), a fast and native macOS hex editor.
 

From 11e5761861e9448667b13b419e92f63139bcb360 Mon Sep 17 00:00:00 2001
From: Finn Voorhees <finnvoorhees@gmail.com>
Date: Tue, 5 Sep 2023 09:13:20 -0400
Subject: [PATCH 6/9] Use xcbeautify for actions (#7)

---
 .github/workflows/build.yml | 6 +++++-
 .github/workflows/test.yml  | 6 +++++-
 2 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 1cefe4a..3e86719 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -26,5 +26,9 @@ jobs:
 
     steps:
       - uses: actions/checkout@v3
+      - name: Install xcbeautify
+        run: |
+          brew update
+          brew install xcbeautify
       - name: Build platform ${{ matrix.destination }}
-        run: set -o pipefail && xcodebuild build -scheme FindFaster -destination "${{ matrix.destination }}" | xcpretty
+        run: set -o pipefail && xcodebuild build -scheme FindFaster -destination "${{ matrix.destination }}" | xcbeautify --renderer github-actions
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 051ce74..2369afa 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -17,5 +17,9 @@ jobs:
     runs-on: macOS-13
     steps:
       - uses: actions/checkout@v3
+      - name: Install xcbeautify
+        run: |
+          brew update
+          brew install xcbeautify
       - name: Test
-        run: set -o pipefail && xcodebuild test -scheme FindFaster -destination "platform=macOS" | xcpretty
+        run: set -o pipefail && xcodebuild test -scheme FindFaster -destination "platform=macOS" | xcbeautify --renderer github-actions

From 141708a445e438d8019c2031bca3a639db11d0bc Mon Sep 17 00:00:00 2001
From: Finn Voorhees <finnvoorhees@gmail.com>
Date: Thu, 7 Sep 2023 10:38:20 -0400
Subject: [PATCH 7/9] Add support for swift 5.7 (#10)

---
 Package.swift                                               | 2 +-
 Sources/FindFaster/BidirectionalCollection+fastSearch.swift | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/Package.swift b/Package.swift
index 861b36e..625dc31 100644
--- a/Package.swift
+++ b/Package.swift
@@ -1,4 +1,4 @@
-// swift-tools-version: 5.9
+// swift-tools-version: 5.7
 
 import PackageDescription
 
diff --git a/Sources/FindFaster/BidirectionalCollection+fastSearch.swift b/Sources/FindFaster/BidirectionalCollection+fastSearch.swift
index f2f3990..c4a10e9 100644
--- a/Sources/FindFaster/BidirectionalCollection+fastSearch.swift
+++ b/Sources/FindFaster/BidirectionalCollection+fastSearch.swift
@@ -45,9 +45,9 @@ public extension BidirectionalCollection where Element: Equatable, Element: Hash
         onSearchResult: ((Index) -> Void)? = nil
     ) -> [Index] {
         switch searchSequence.count {
-        case 0: []
-        case 1: naiveSingleElementSearch(for: searchSequence.first!, onSearchResult: onSearchResult)
-        default: boyerMooreMultiElementSearch(for: searchSequence, onSearchResult: onSearchResult)
+        case 0: return []
+        case 1: return naiveSingleElementSearch(for: searchSequence.first!, onSearchResult: onSearchResult)
+        default: return boyerMooreMultiElementSearch(for: searchSequence, onSearchResult: onSearchResult)
         }
     }
 }

From 08b043d73c0b49e51a167f1dbcc9832361678a3b Mon Sep 17 00:00:00 2001
From: Finn Voorhees <finnvoorhees@gmail.com>
Date: Tue, 12 Sep 2023 15:33:01 +0100
Subject: [PATCH 8/9] Combine workflows

---
 .github/workflows/{build.yml => CI.yml} | 13 ++++++++++++-
 .github/workflows/test.yml              | 25 -------------------------
 2 files changed, 12 insertions(+), 26 deletions(-)
 rename .github/workflows/{build.yml => CI.yml} (68%)
 delete mode 100644 .github/workflows/test.yml

diff --git a/.github/workflows/build.yml b/.github/workflows/CI.yml
similarity index 68%
rename from .github/workflows/build.yml
rename to .github/workflows/CI.yml
index 3e86719..31ba163 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/CI.yml
@@ -1,4 +1,4 @@
-name: Build
+name: CI
 
 on:
   push:
@@ -32,3 +32,14 @@ jobs:
           brew install xcbeautify
       - name: Build platform ${{ matrix.destination }}
         run: set -o pipefail && xcodebuild build -scheme FindFaster -destination "${{ matrix.destination }}" | xcbeautify --renderer github-actions
+  test:
+    name: Test
+    runs-on: macOS-13
+    steps:
+      - uses: actions/checkout@v3
+      - name: Install xcbeautify
+        run: |
+          brew update
+          brew install xcbeautify
+      - name: Test
+        run: set -o pipefail && xcodebuild test -scheme FindFaster -destination "platform=macOS" | xcbeautify --renderer github-actions
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
deleted file mode 100644
index 2369afa..0000000
--- a/.github/workflows/test.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-name: Test
-
-on:
-  push:
-    branches:
-      - main
-  pull_request:
-    branches:
-      - main
-
-env:
-  DEVELOPER_DIR: /Applications/Xcode_15.0.app/Contents/Developer
-
-jobs:
-  test:
-    name: Test
-    runs-on: macOS-13
-    steps:
-      - uses: actions/checkout@v3
-      - name: Install xcbeautify
-        run: |
-          brew update
-          brew install xcbeautify
-      - name: Test
-        run: set -o pipefail && xcodebuild test -scheme FindFaster -destination "platform=macOS" | xcbeautify --renderer github-actions

From afeb7f4082dd66798ac84b196bb8997825977ecf Mon Sep 17 00:00:00 2001
From: Finn Voorhees <finnvoorhees@gmail.com>
Date: Tue, 12 Sep 2023 15:44:17 +0100
Subject: [PATCH 9/9] Add status badge to README (#11)

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 3141d65..e049d96 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
 # FindFaster
 
-[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FFinnvoor%2FFindFaster%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/Finnvoor/FindFaster) [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FFinnvoor%2FFindFaster%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/Finnvoor/FindFaster)
+[![CI](https://github.com/Finnvoor/FindFaster/actions/workflows/CI.yml/badge.svg)](https://github.com/Finnvoor/FindFaster/actions/workflows/CI.yml) [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FFinnvoor%2FFindFaster%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/Finnvoor/FindFaster) [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FFinnvoor%2FFindFaster%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/Finnvoor/FindFaster)
 
 Fast asynchronous swift collection search using the [_Boyer–Moore string-search algorithm_](https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string-search_algorithm).  `fastSearch` can be used with any `BidirectionalCollection` where `Element` is `Hashable`, and is especially useful for searching large amounts of data or long strings and displaying the results as they come in.