diff --git a/Documentation/Extensions.md b/Documentation/Extensions.md index ad19611..52c37bd 100644 --- a/Documentation/Extensions.md +++ b/Documentation/Extensions.md @@ -676,7 +676,445 @@ bytes.string // "hi" ## Collections & Sequences - +
+Array — dedup, mutation helpers (Element: Equatable) + +### `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(keyPath:) -> [Element]` / `(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 +``` + +
+ +
+Collection — safe indexing, chunking, indices, averages + +### `var fullRange: Range` +`startIndex.. 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… +``` + +
+ +
+BidirectionalCollection + +### `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(where keyPath:, equals value:) -> Element?` +Last element whose key-path value equals `value`. + +```swift +events.last(where: \.type, equals: .login) +``` + +
+ +
+Sequence — predicates, key-path sorting, sums, dedup + +### `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(initial:next:) -> [U]` +Like `reduce` but returns every interim result. + +```swift +[1, 2, 3].accumulate(initial: 0, next: +) // [1, 3, 6] +``` + +### `func filtered(_ 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(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(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(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] +``` + +
+ +
+RangeReplaceableCollection — rotate, take/skip, offset subscripts + +### `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(keyPath:)` +In-place dedup by an `Equatable` or `Hashable` key path. + +### `subscript(offset: Int) -> Element` / `subscript(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.none) // [1] +a.appendIfNonNil(2) // [1, 2] +``` + +
+ +
+Dictionary — key paths, JSON, key/value maps, merge operators + +### `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() 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(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]` / `func compactMapKeysAndValues(_:) -> [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. + +
+ +
+Optional — safe unwrap, conditional assignment + +### `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 +``` + +
+ +
+Comparable + +### `func isBetween(_ range: ClosedRange) -> Bool` +Range membership. + +```swift +7.isBetween(6...12) // true +``` + +### `func clamped(to range: ClosedRange) -> Self` +Constrain a value to a range. + +```swift +1.clamped(to: 3...8) // 3 +0.32.clamped(to: 0.1...0.29) // 0.29 +``` + +
+ +
+Bool + +### `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" +``` + +
## Numbers & Geometry