[docs] Extensions.md: Collections & Sequences section
This commit is contained in:
@@ -676,7 +676,445 @@ bytes.string // "hi"
|
|||||||
|
|
||||||
## Collections & Sequences
|
## Collections & Sequences
|
||||||
|
|
||||||
<!-- batch 4 -->
|
<details>
|
||||||
|
<summary><b>Array</b> — dedup, mutation helpers (<code>Element: Equatable</code>)</summary>
|
||||||
|
|
||||||
|
### `var unique: [Element]` / `func withoutDuplicates() -> [Element]`
|
||||||
|
New array with duplicates dropped, first occurrence kept (order preserved).
|
||||||
|
|
||||||
|
```swift
|
||||||
|
[1, 2, 2, 3].unique // [1, 2, 3]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `mutating func removeDuplicates() -> [Element]`
|
||||||
|
In-place dedup (`@discardableResult`).
|
||||||
|
|
||||||
|
### `func withoutDuplicates<E: Equatable>(keyPath:) -> [Element]` / `<E: Hashable>(keyPath:)`
|
||||||
|
Dedup by a key path. The `Hashable` overload is O(n).
|
||||||
|
|
||||||
|
```swift
|
||||||
|
users.withoutDuplicates(keyPath: \.id)
|
||||||
|
```
|
||||||
|
|
||||||
|
### `var removeNilElements: [Element]`
|
||||||
|
`compactMap { $0 }` — only meaningful when `Element` is itself optional.
|
||||||
|
|
||||||
|
### `mutating func removeAll(_ item: Element) -> [Element]` / `removeAll(_ items: [Element])`
|
||||||
|
Remove every occurrence of one value / of any value in a list (`@discardableResult`).
|
||||||
|
|
||||||
|
```swift
|
||||||
|
var a = [1, 2, 2, 3]; a.removeAll(2) // [1, 3]
|
||||||
|
var b = [1, 2, 3, 4]; b.removeAll([2, 4]) // [1, 3]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `mutating func prepend(_ newElement: Element)`
|
||||||
|
Insert at index 0.
|
||||||
|
|
||||||
|
### `mutating func safeSwap(from:to:)`
|
||||||
|
Swap two indices; silently no-ops if either is out of bounds or equal.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
var a = [1, 2, 3]; a.safeSwap(from: 0, to: 2) // [3, 2, 1]
|
||||||
|
a.safeSwap(from: 0, to: 9) // unchanged
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>Collection</b> — safe indexing, chunking, indices, averages</summary>
|
||||||
|
|
||||||
|
### `var fullRange: Range<Index>`
|
||||||
|
`startIndex..<endIndex`.
|
||||||
|
|
||||||
|
### `subscript(safe index:) -> Element?` / `subscript(exist index:) -> Element?`
|
||||||
|
Bounds-checked access, `nil` instead of a crash.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
let a = [1, 2, 3]
|
||||||
|
a[safe: 1] // 2
|
||||||
|
a[safe: 9] // nil
|
||||||
|
```
|
||||||
|
|
||||||
|
### `func group(by size: Int) -> [[Element]]?`
|
||||||
|
Split into chunks of `size` (last chunk may be shorter). `nil` when empty or `size <= 0`.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
[0, 2, 4, 7, 6].group(by: 2) // [[0, 2], [4, 7], [6]]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `func forEach(slice: Int, body: ([Element]) -> Void)`
|
||||||
|
Iterate in chunks without building the intermediate array.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
[0, 2, 4, 7].forEach(slice: 2) { print($0) } // [0, 2] then [4, 7]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `func indices(where condition:) -> [Index]?`
|
||||||
|
All indices matching a predicate, `nil` if none.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
[1, 7, 1, 2, 1].indices(where: { $0 == 1 }) // [0, 2, 4]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `func indices(of item: Element) -> [Index]` — *(`Element: Equatable`)*
|
||||||
|
All indices equal to `item`.
|
||||||
|
|
||||||
|
### `func adjacentPairs() -> AnySequence<(Element, Element)>`
|
||||||
|
Every unordered pair `(i, j)` with `i` before `j`.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
Array([1, 2, 3].adjacentPairs()) // [(1, 2), (1, 3), (2, 3)]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `func forEachInParallel(_:)`
|
||||||
|
`DispatchQueue.concurrentPerform` over the elements. No ordering guarantee.
|
||||||
|
|
||||||
|
### `func average() -> Double` *(`Element: BinaryInteger`)* / `func average() -> Element` *(`Element: FloatingPoint`)*
|
||||||
|
Mean, `0` for an empty collection.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
[1, 2, 3, 4].average() // 2.5
|
||||||
|
[1.2, 2.3, 4.5].average() // 2.666…
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>BidirectionalCollection</b></summary>
|
||||||
|
|
||||||
|
### `subscript(offset distance: Int) -> Element`
|
||||||
|
Positive offset from the start, negative from the end.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
let a = [1, 2, 3, 4, 5]
|
||||||
|
a[offset: 1] // 2
|
||||||
|
a[offset: -2] // 4
|
||||||
|
```
|
||||||
|
|
||||||
|
### `func last<T: Equatable>(where keyPath:, equals value:) -> Element?`
|
||||||
|
Last element whose key-path value equals `value`.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
events.last(where: \.type, equals: .login)
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>Sequence</b> — predicates, key-path sorting, sums, dedup</summary>
|
||||||
|
|
||||||
|
### `func all(matching:)` / `func none(matching:)` / `func any(matching:)`
|
||||||
|
Whether the predicate holds for all / no / at least one element.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
[2, 4, 6].all(matching: { $0 % 2 == 0 }) // true
|
||||||
|
[1, 3].any(matching: { $0 % 2 == 0 }) // false
|
||||||
|
```
|
||||||
|
|
||||||
|
### `func reject(where:) -> [Element]`
|
||||||
|
Inverse of `filter`.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
[2, 3, 4, 7].reject(where: { $0 % 2 == 0 }) // [3, 7]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `func count(where:) -> Int`
|
||||||
|
Number of elements matching a predicate.
|
||||||
|
|
||||||
|
### `func forEachReversed(_:)` / `func forEach(where:body:)`
|
||||||
|
Iterate right-to-left / iterate only matching elements.
|
||||||
|
|
||||||
|
### `func accumulate<U>(initial:next:) -> [U]`
|
||||||
|
Like `reduce` but returns every interim result.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
[1, 2, 3].accumulate(initial: 0, next: +) // [1, 3, 6]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `func filtered<T>(_ isIncluded:, map transform:) -> [T]`
|
||||||
|
`filter` + `map` in one lazy pass.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
[1, 2, 3, 4].filtered({ $0 % 2 == 0 }, map: { "\($0)" }) // ["2", "4"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `func single(where:) -> Element?`
|
||||||
|
The one matching element, or `nil` if zero or more than one match.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
[1, 4, 7].single(where: { $0 % 2 == 0 }) // 4
|
||||||
|
[2, 4].single(where: { $0 % 2 == 0 }) // nil
|
||||||
|
```
|
||||||
|
|
||||||
|
### `func divided(by condition:) -> (matching:, nonMatching:)`
|
||||||
|
Partition into two arrays.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
let (even, odd) = [0, 1, 2, 3].divided { $0 % 2 == 0 } // ([0, 2], [1, 3])
|
||||||
|
```
|
||||||
|
|
||||||
|
### `func withoutDuplicates<T: Hashable>(transform:) -> [Element]`
|
||||||
|
Dedup by a derived hashable value.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
[(1, "a"), (2, "b"), (1, "c")].withoutDuplicates { $0.0 } // [(1, "a"), (2, "b")]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `func sorted(by keyPath:)` / `sorted(by keyPath:with:)` / `sorted(by:and:)` / `sorted(by:and:and:)`
|
||||||
|
Sort by one, two, or three key paths (later paths break ties). The `with:`
|
||||||
|
variant takes an explicit comparator.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
people.sorted(by: \.lastName, and: \.firstName)
|
||||||
|
scores.sorted(by: \.value, with: >)
|
||||||
|
```
|
||||||
|
|
||||||
|
### `func sum() -> Element` *(`Element: AdditiveArithmetic`)* / `func sum<T: AdditiveArithmetic>(for keyPath:) -> T`
|
||||||
|
Total of the elements, or of a numeric property.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
[1, 2, 3].sum() // 6
|
||||||
|
["ab", "cde"].sum(for: \.count) // 5
|
||||||
|
```
|
||||||
|
|
||||||
|
### `func first<T: Equatable>(where keyPath:, equals value:) -> Element?`
|
||||||
|
First element matching on a key path.
|
||||||
|
|
||||||
|
### `func contains(_ elements: [Element]) -> Bool` *(`Element: Equatable` or `Hashable`)*
|
||||||
|
Whether every element of `elements` is present (the `Hashable` overload is O(m+n)).
|
||||||
|
|
||||||
|
### `func containsDuplicates() -> Bool` / `func duplicates() -> [Element]` *(`Element: Hashable`)*
|
||||||
|
Whether any value repeats / the set of repeated values.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
[1, 2, 2, 3, 3].duplicates().sorted() // [2, 3]
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>RangeReplaceableCollection</b> — rotate, take/skip, offset subscripts</summary>
|
||||||
|
|
||||||
|
### `init(expression:count:)`
|
||||||
|
Build a collection by evaluating an autoclosure `count` times.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
Array(expression: Int.random(in: 0..<10), count: 3) // e.g. [4, 9, 1]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `func rotated(by places: Int) -> Self` / `mutating func rotate(by:) -> Self`
|
||||||
|
Rotate elements; positive moves the tail to the front.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
[1, 2, 3, 4].rotated(by: 1) // [4, 1, 2, 3]
|
||||||
|
[1, 2, 3, 4].rotated(by: -1) // [2, 3, 4, 1]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `mutating func removeFirst(where:) -> Element?`
|
||||||
|
Remove and return the first match (`@discardableResult`).
|
||||||
|
|
||||||
|
### `mutating func removeRandomElement() -> Element?`
|
||||||
|
Remove and return a random element.
|
||||||
|
|
||||||
|
### `mutating func keep(while:) -> Self` / `func take(while:) -> Self` / `func skip(while:) -> Self`
|
||||||
|
`keep`/`take` return the leading run that matches; `skip` returns everything
|
||||||
|
after it.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
[0, 2, 4, 7, 6].take(while: { $0 % 2 == 0 }) // [0, 2, 4]
|
||||||
|
[0, 2, 4, 7, 6].skip(while: { $0 % 2 == 0 }) // [7, 6]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `mutating func removeDuplicates<E>(keyPath:)`
|
||||||
|
In-place dedup by an `Equatable` or `Hashable` key path.
|
||||||
|
|
||||||
|
### `subscript(offset: Int) -> Element` / `subscript<R: RangeExpression>(range:) -> SubSequence`
|
||||||
|
Get/set by integer offset or integer range.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
var a = [10, 20, 30]; a[1] = 99 // [10, 99, 30]
|
||||||
|
a[0..<2] // [10, 99]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `mutating func appendIfNonNil(_:)` / `appendIfNonNil(contentsOf:)`
|
||||||
|
Append only when the optional value / sequence is non-nil.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
var a = [1]; a.appendIfNonNil(Optional<Int>.none) // [1]
|
||||||
|
a.appendIfNonNil(2) // [1, 2]
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>Dictionary</b> — key paths, JSON, key/value maps, merge operators</summary>
|
||||||
|
|
||||||
|
### `subscript(path path: [Key]) -> Any?`
|
||||||
|
Deep get/set through nested dictionaries.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
var d: [String: Any] = ["a": ["b": ["c": 1]]]
|
||||||
|
d[path: ["a", "b", "c"]] // 1
|
||||||
|
d[path: ["a", "b", "c"]] = 2
|
||||||
|
```
|
||||||
|
|
||||||
|
### `var queryString: String`
|
||||||
|
`key=value&key=value` (no percent-encoding — encode yourself if needed).
|
||||||
|
|
||||||
|
```swift
|
||||||
|
["a": 1, "b": 2].queryString // "a=1&b=2" (order not guaranteed)
|
||||||
|
```
|
||||||
|
|
||||||
|
### `var convertToJSON: String`
|
||||||
|
Pretty-printed JSON, or an error description string on failure.
|
||||||
|
|
||||||
|
### `init(grouping sequence: by keyPath:)`
|
||||||
|
Group a sequence into `[Key: [Element]]` by a key path.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
Dictionary(grouping: people, by: \.city)
|
||||||
|
```
|
||||||
|
|
||||||
|
### `func toObjetct<T: Codable & Sendable>() throws -> T`
|
||||||
|
Encode to JSON then decode into `T`. Throws `DecodingError` on a shape mismatch.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
let user: User = try ["id": 1, "name": "Ana"].toObjetct()
|
||||||
|
```
|
||||||
|
|
||||||
|
### `func has(key:) -> Bool`
|
||||||
|
Key presence check.
|
||||||
|
|
||||||
|
### `mutating func removeAll<S: Sequence>(keys:)` / `static func - (lhs:keys:)` / `static func -= (lhs:keys:)`
|
||||||
|
Remove a set of keys — mutating, or as a new dictionary via `-` / `-=`.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
var d = ["a": 1, "b": 2, "c": 3]
|
||||||
|
d -= ["a", "b"] // ["c": 3]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `mutating func removeValueForRandomKey() -> Value?`
|
||||||
|
Remove and return one random entry's value.
|
||||||
|
|
||||||
|
### `func jsonData(prettify: Bool = false) -> Data?` / `func jsonString(prettify: Bool = false) -> String?`
|
||||||
|
Serialise to `Data` / `String`, `nil` if the dictionary isn't a valid JSON object.
|
||||||
|
|
||||||
|
### `func mapKeysAndValues<K, V>(_:) -> [K: V]` / `func compactMapKeysAndValues<K, V>(_:) -> [K: V]`
|
||||||
|
Transform both keys and values in one pass; the `compact` variant drops `nil` results.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
["a": 1].mapKeysAndValues { ($0.key.uppercased(), $0.value * 10) } // ["A": 10]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `func pick(keys: [Key]) -> [Key: Value]`
|
||||||
|
Sub-dictionary limited to the given keys.
|
||||||
|
|
||||||
|
### `static func + (lhs:rhs:)` / `static func += (lhs:rhs:)`
|
||||||
|
Merge two dictionaries (right wins on key clash).
|
||||||
|
|
||||||
|
### `func keys(forValue value:) -> [Key]` *(`Value: Equatable`)*
|
||||||
|
All keys mapping to a value.
|
||||||
|
|
||||||
|
### `mutating func lowercaseAllKeys()` *(`Key: StringProtocol`)*
|
||||||
|
Lowercase every key in place.
|
||||||
|
|
||||||
|
### `var uniqueValues: [Key: Value]` *(`Value: Hashable`)*
|
||||||
|
Keep only the first entry seen for each distinct value.
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>Optional</b> — safe unwrap, conditional assignment</summary>
|
||||||
|
|
||||||
|
### `func unwrapped(or defaultValue: Wrapped) -> Wrapped`
|
||||||
|
`self ?? defaultValue`, read nicely.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
let name: String? = nil
|
||||||
|
name.unwrapped(or: "Guest") // "Guest"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `func unwrapped(or error: Error) throws -> Wrapped`
|
||||||
|
Unwrap or throw a chosen error.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
let id = try userId.unwrapped(or: AppError.missingID)
|
||||||
|
```
|
||||||
|
|
||||||
|
### `func run(_ block: (Wrapped) -> Void)`
|
||||||
|
Run a block only when non-nil (like `if let`, expression-style).
|
||||||
|
|
||||||
|
```swift
|
||||||
|
token.run { print("have token \($0)") }
|
||||||
|
```
|
||||||
|
|
||||||
|
### `static func ??= (lhs: inout Optional, rhs: Optional)`
|
||||||
|
Assign only if the right side is non-nil.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
var params: String? = "a"; params ??= nil // still "a"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `static func ?= (lhs: inout Optional, rhs: @autoclosure)`
|
||||||
|
Assign only if the left side is currently nil.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
var text: String? = nil
|
||||||
|
text ?= "first" // "first"
|
||||||
|
text ?= "second" // still "first"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `var isNilOrEmpty: Bool` / `var nonEmpty: Wrapped?` *(`Wrapped: Collection`)*
|
||||||
|
Nil-or-empty check; `nonEmpty` returns the collection only when it has content.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
let list: [Int]? = []
|
||||||
|
list.isNilOrEmpty // true
|
||||||
|
list.nonEmpty // nil
|
||||||
|
```
|
||||||
|
|
||||||
|
### `static func == / != (Optional, Wrapped.RawValue?)` *(`Wrapped: RawRepresentable`)*
|
||||||
|
Compare an optional enum directly against an optional raw value.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
let status: Status? = .active
|
||||||
|
status == "active" // true
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>Comparable</b></summary>
|
||||||
|
|
||||||
|
### `func isBetween(_ range: ClosedRange<Self>) -> Bool`
|
||||||
|
Range membership.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
7.isBetween(6...12) // true
|
||||||
|
```
|
||||||
|
|
||||||
|
### `func clamped(to range: ClosedRange<Self>) -> Self`
|
||||||
|
Constrain a value to a range.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
1.clamped(to: 3...8) // 3
|
||||||
|
0.32.clamped(to: 0.1...0.29) // 0.29
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><b>Bool</b></summary>
|
||||||
|
|
||||||
|
### `var int: Int` / `var string: String` / `var data: Data`
|
||||||
|
`1`/`0`, `"true"`/`"false"`, or a single-byte `Data`.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
true.int // 1
|
||||||
|
false.string // "false"
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## Numbers & Geometry
|
## Numbers & Geometry
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user