CollectionExtension is a versatile collection of extensions and utilities for arrays, dictionaries, sequences, and various collection types in Swift. These extensions offer added functionality for performing various operations on collections.
To run the example project, clone the repo, and run pod install
from the Example directory first.
- Swift 5.0 or higher (or 5.3 when using Swift Package Manager)
- iOS 10.0 or higher
- macOS 10.0 or higher (for Swift Package Manager)
- tvOS 10.10 or higher (for Swift Package Manager)
CollectionExtension is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'CollectionExtension'
To install using Xcode's Swift Package Manager, follow these steps:
- Go to File > Swift Package > Add Package Dependency
- Enter the URL: https://github.com/hainayanda/CollectionExtension.git
- Choose Up to Next Major for the version rule and set the version to 1.1.0.
- Click "Next" and wait for the package to be fetched.
If you prefer using Package.swift, add CollectionExtension as a dependency in your Package.swift file:
dependencies: [
.package(url: "https://github.com/hainayanda/CollectionExtension.git", .upToNextMajor(from: "1.1.0"))
]
Then, include it in your target:
.target(
name: "MyModule",
dependencies: ["CollectionExtension"]
)
The safe subscript allows you to access elements with an index, returning nil if the index is out of bounds:
let safeResult = myArray[safe: 100] // Returns nil if out of bounds
myArray[safe: 100] = someValue // Safely sets the value, does nothing if out of bounds or appends if index equals count
myArray[safe: 10] = nil // Safely removes the element
Type erasing a sequence is made easy:
let typeErased: AnySequence<Some> = myArray.eraseToAnySequence()
let lazyTypeErased: AnyLazySequence<Some> = myArray.lazy.eraseToAnyLazySequence()
Easily filter duplicated elements from a sequence while preserving order:
let uniqueArray = myArray.unique // Removes duplicates and keeps order
You can also specify custom comparison logic if your elements are not equatable:
let uniqueArray = myArray.distinct { $0.identifier } // Using projection
let uniqueArray = myArray.distinct { $0.identifier == $1.identifier } // Using closure
let uniqueArray = myArray.distinct(by: \.identifier) // Using keypath
let uniqueArray = myArray.uniqueInstances // Using object identifier
Create a new array by adding an element:
let myArray = [1, 2]
let oneToThree = myArray.added(with: 3) // [1, 2, 3]
let oneToFive = myArray.added(withContentsOf: [4, 5]) // [1, 2, 3, 4, 5]
Append or insert distinct elements while maintaining order:
let myArray = [3, 4]
myArray.appendIfDistinct(4) // Appends 4 if not already present
myArray.appendAllDistinct(in: [4, 5]) // Appends all distinct elements
myArray.insertIfDistinct(4, at: 0) // Inserts 4 if not already present
myArray.insertAllDistinct(in: [1, 2, 3, 4], at: 0) // Inserts all distinct elements
You can also create new arrays instead of modifying the original:
let addedArray = myArray.addedIfDistinct(4)
You can also specify custom comparison logic if your elements are not equatable:
myArray.appendIfDistinct(some) { $0.identifier } // Using projection
myArray.appendIfDistinct(some) { $0.identifier == $1.identifier } // Using closure
myArray.appendIfDistinct(some, using: \.identifier) // Using keypath
myArray.appendIfDistinctInstance(some) // Using object identifier
Perform set operations with ease while maintaining order:
let subtracted = left.subtract(by: right) // Subtract elements from left
let intersect = left.intersect(by: right) // Get the intersection of elements
let symmetricDifference = left.symmetricDifference(with: right) // Get symmetric difference
You can also specify custom comparison logic if your elements are not equatable:
let substracted myArray.substract(by: some) { $0.identifier } // Using projection
let substracted myArray.substract(by: some) { $0.identifier == $1.identifier } // Using closure
let substracted myArray.substract(by: some, by: \.identifier) // Using keypath
let substracted myArray.substractInstances(some) // Using object identifier
Drop elements until or after a specific condition is met:
let array = [1, 2, 3, 4, 5]
let subtracted = left.dropAllUntil { $0 == 3 } // Drops until condition is met
let subtracted = left.dropAllAfter { $0 == 3 } // Drops after condition is met
if the element is equatable you can do this:
let array = [1, 2, 3, 4, 5]
let substracted1 = left.dropAllUntil(find: 3)
let substracted2 = left.dropAllAfter(find: 3)
Calculate basic statistics like sum, median, average, and more:
let array = [1, 2, 3, 4, 5]
let median: Median<Int> = array.median // Calculate median (.single(3))
let sum = array.sum // Calculate sum (15)
let average = array.average // Calculate average (3)
let smallest = array.smallest // Find the smallest element (1)
let biggest = array.biggest // Find the largest element (5)
We can count elements like modus and frequency too:
let array = [1, 1, 1, 2, 2, 3, 3, 3, 3]
let modus = array.modus // Search modus
let twoCount = array.elementCount(2) // Calculate count of 2
let group = array.groupedByFrequency() // Group by frequency ([1: 2, 2: 2, 3: 4])
You can also specify custom comparison logic if your elements are not equatable:
// using projection
myArray.modus { $0.identifier } // Using projection
myArray.modus { $0.identifier == $1.identifier } // Using closure
myArray.modus(by: \.identifier) // Using keypath
myArray.modusInstances // Using object identifier
Group elements based on a condition:
let array = [1, 2, 3, 4, 5]
let group = array.group { $0 % 2 == 0 ? "even": "odd" } // Group by condition (["even": [2, 4], "odd": [1, 3, 5]])
Or using keyPath:
let group = array.group(by: \.someProperty)
Transform a sequence into a dictionary:
let group1 = try array.transformToDictionary { $0.identifier } // Using closure
let group2 = try array.transformToDictionary(keyedBy: \.identifier) // Using keypath
If the key is duplicated, it will throw CollectionExtensionError.duplicatedKey
Transform dictionary keys while keeping the values:
let result = myDictionary.mapKeys { $0.identifier } // Map keys
If the key is duplicated, it will throw CollectionExtensionError.duplicatedKey
If you want to overwrite the duplicated key with the next value found, use overwriteMapKeys
instead:
let result = myDictionary.overwriteMapKeys { $0.identifier }
You can use compactMapKeys
if you want to ignore the key that cannot produce a value:
let result = myDictionary.compactMapKeys { $0.identifier }
If the key is duplicated, it will throw CollectionExtensionError.duplicatedKey
If you want to overwrite the duplicated key with the next value found while using compactMapKeys
, use overwriteCompactMapKeys
instead:
let result = myDictionary.overwriteCompactMapKeys { $0.identifier }
If you need a key and value to produce a new key, use:
mapKeyValues
instead ofmapKeys
overwriteMapKeysValues
instead ofoverwriteMapKeys
compactMapKeysValues
instead ofcompactMapKeys
overwriteCompactMapKeysValues
instead ofoverwriteCompactMapKeys
Iterate asynchronously while preserving sequence order:
array.asyncForEach { element in
await element.someTask()
}
array.asyncForEachIgnoreError { element in
try await element.someTaskWithError()
}
array.asyncMap { element in
await element.produceSomeValue()
}
array.asyncMapSkipError { element in
try await element.produceSomeValueWithError()
}
You can also use Combine Future instead of async/await:
futureForEach
instead ofasyncForEach
futureForEachIgnoreError
instead ofasyncForEachIgnoreError
futureMap
instead ofasyncMap
futureMapSkipError
instead ofasyncMapSkipError
futureCompactMap
instead ofasyncCompactMap
futureCompactMapSkipError
instead ofasyncCompactMapSkipError
The library provides a DoublyLinkedList class for further collection management.
Feel free to contribute by cloning the repository and creating pull requests.
Nayanda Haberty, hainayanda@outlook.com
CombineAsync is available under the MIT license. See the LICENSE file for more info.