# Swift Async Algorithms

`swift-async-algorithms` is an open-source Apple package that brings the
familiar `Sequence` algorithms, plus time-based ones, to `AsyncSequence`.

```swift
// Package.swift
dependencies: [
    .package(
        url: "https://github.com/apple/swift-async-algorithms",
        .upToNextMajor(from: "1.0.0")
    )
],
targets: [
    .target(
        name: "Feed",
        dependencies: [.product(name: "AsyncAlgorithms", package: "swift-async-algorithms")]
    )
]
```

Then `import AsyncAlgorithms` in the source files that use it.

## Combining sequences

| Function | Result |
|---|---|
| `merge(first, second)` | One sequence with elements from both, in the order they arrive |
| `combineLatest(first, second)` | The most recent pair, emitted whenever either side produces |
| `zip(first, second)` | Elements paired one to one; waits until both sides have one |
| `chain(first, second)` | All of `first`, then all of `second` |

## Time-based operators

| Operator | Behavior | Typical use |
|---|---|---|
| `.debounce(for: .milliseconds(250))` | Emits only after the input goes quiet for the interval | Search fields |
| `.chunks(ofCount: 20)` | Arrays of up to 20 elements | Batch writes |
| `.chunked(by: .repeating(every: .seconds(5)))` | Arrays collected per time window | Periodic flushes |
| `.chunks(ofCount: 25, or: .repeating(every: .seconds(2)))` | A batch when it is full or when the timer fires, whichever comes first | Batch uploads with a latency cap |

Throttling ships in the 1.x releases only as the underscored `_throttle(for:)`,
which is not stable public API; do not build on it.

## Filtering and cleanup

- `.removeDuplicates()` drops an element equal to the one just before it.
- `.compacted()` removes `nil` from a sequence of optionals, the same as
  `compactMap { $0 }`.

## Patterns

### Search as the user types

```swift
func suggestions(for queries: AsyncStream<String>) -> AsyncStream<[Place]> {
    AsyncStream { continuation in
        let worker = Task {
            for await query in queries.debounce(for: .milliseconds(250)).removeDuplicates() {
                if Task.isCancelled { break }
                let places = (try? await geocoder.search(query)) ?? []
                continuation.yield(places)
            }
            continuation.finish()
        }
        continuation.onTermination = { _ in worker.cancel() }
    }
}
```

### Batched uploads

```swift
for await batch in pendingEvents.chunks(ofCount: 25, or: .repeating(every: .seconds(2))) {
    try await analytics.upload(batch)
}
```
