Swift has generic Collections and Sequences as well as some universal free functions to work with them. What is missing is a fluent interface that would make working with them easy¹ - like list comprehensions in many languages or LINQ in .NET. The operations should: require no typecasts, be easily chained, work on any sequences, be performed lazily where possible.
SINQ (or LINQ for Swift) is a Swift library for working with sequences / collections. It is, as name suggests, modelled after LINQ, but it is not necessarily intended to be a LINQ port. The library is still under development, just as Swift is. Any contributions, both in terms of suggestions/ideas or actual code are welcome.
SINQ is brought to you by Leszek Ślażyński (slazyk), you can follow me on twitter and github. Be sure to check out Observable-Swift my other library that implements value observing and events.
The main goal of SINQ is to provide a fluent interface for working with collections. The way it tries to accomplish that is with chaining of methods. Most of the operations are performed lazily, i.e. computations are deferred and done only for the part of the result you enumerate. Everything is typed - no typecasts required. Examples:
let nums1 = from([1, 4, 2, 3, 5]).whereTrue{ $0 > 2 }.orderBy{ $0 }.select{ 2 * $0 }
// or less (linq | sql)-esque
let nums2 = sinq([1, 4, 2, 3, 5]).filter{ $0 > 2 }.orderBy{ $0 }.map{ 2 * $0 }
// path1 : String = ..., path2: String = ...
let commonComponents = sinq(path1.pathComponents)
.zip(path2.pathComponents) { ($0, $1) }
.takeWhile { $0 == $1 }
.count()
let prefixLength = sinq(path1).zip(path2){ ($0, $1) }.takeWhile{ $0 == $1 }.count()
// available : String[] = ..., blacklist : String[] = ...
let next = sinq(available).except(blacklist){ $0 }.firstOrDefault("unknown")
// employees : Employee[] = ...
let headCnt = sinq(employees).groupBy{ $0.manager }.map{ ($0.key, $0.values.count()) }
let allTasks = from(employees).selectMany{ $0.tasks }.orderBy{ $0.priority }
let elite1 = sinq(employees).whereTrue{ $0.salary > 1337 }.orderBy{ $0.salary }
let elite2 = from(employees).orderBy{ $0.salary }.takeWhile{ $0.salary > 1337 }
// products : Product[] = ..., categories : Category[] = ...
let breadcrumbs = sinq(categories).join(inner: products,
outerKey: { $0.id },
innerKey: { $0.categoryId },
result: { "\($0.name) / \($1.name)" })
Please note that the results are not cached, i.e. looping twice over result of orderBy(...)
will perform two sorts. If you want to use results multiple times, you can get always array with toArray()
.
It uses SinqSequence<T>
wrapper struct in order to do that, you can wrap any Sequence
by simply sinq(seq)
, from(seq)
, or SinqSequence(seq)
. This wrapper is introduced because Swift does not allow for adding methods to protocols (like Sequence
) and because extending existing SequenceOf<T>
causes linker errors.
While I do try to follow cocoa-like naming and spelling conventions, while also keeping the LINQ naming where reasonable, I refuse to call the struct SINQSequence<T>
or SSequence<T>
.
You can use either CocoaPods or Carthage to install SINQ.
Otherwise, the easiest option to use SINQ in your project is to clone this repo and add SINQ.xcodeproj to your project/workspace and then add SINQ.framework to frameworks for your target.
After that you just import SINQ
.
aggregate(combine: (T, T) -> T) -> T
aggregate<R>(initial: R, combine: (R, T) -> R) -> R
aggregate<C, R>(initial: C, combine: (C, T) -> C, result: C -> R) -> R
all(predicate: T -> Bool) -> Bool
any() -> Bool
any(predicate: T -> Bool) -> Bool
concat<S: Sequence>(other: S) -> SinqSequence<T>
contains(value: T, equality: (T, T) -> Bool) -> Bool
contains<K: Equatable>(value: T, key: T -> K) -> Bool
func count() -> Int
distinct(equality: (T, T) -> Bool) -> SinqSequence<T>
distinct<K: Hashable>(key: T -> K) -> SinqSequence<T>
each(function: T -> ()) -> ()
elementAtOrNil(index: Int) -> T?
elementAt(index: Int) -> T
elementAt(index: Int, orDefault: T) -> T
except<S: Sequence>(sequence: S, equality: (T, T) -> Bool) -> SinqSequence<T>
except<S: Sequence, K: Hashable>(sequence: S, key: T -> K) -> SinqSequence<T>
first() -> T
firstOrNil() -> T?
first(predicate: T -> Bool) -> T
firstOrDefault(defaultElement: T) -> T
firstOrNil(predicate: T -> Bool) -> T?
firstOrDefault(defaultElement: T, predicate: T -> Bool) -> T
groupBy<K: Hashable>(key: T -> K) -> SinqSequence<Grouping<K, T>>
groupBy<K: Hashable, V>(key: T -> K, element: T -> V) -> SinqSequence<Grouping<K, V>>
groupBy<K: Hashable, V, R>(key: T -> K, element: T -> V, result: (K, SinqSequence<V>) -> R) -> SinqSequence<R>
groupJoin<S: Sequence, K: Hashable>
(#inner: S, outerKey: T -> K, innerKey: S.E -> K)
-> SinqSequence<Grouping<T, S.E>>
groupJoin<S: Sequence, K: Hashable, R>
(#inner: S, outerKey: T -> K, innerKey: S.E -> K,
result: (T, SinqSequence<S.E>) -> R) -> SinqSequence<R>
intersect<S: Sequence>(sequence: S, equality: (T, T) -> Bool) -> SinqSequence<T>
intersect<S: Sequence, K: Hashable>(sequence: S, key: T -> K) -> SinqSequence<T>
join<S: Sequence, K: Hashable, R>
(#inner: S, outerKey: T -> K, innerKey: S.E -> K,
result: (T, S.E) -> R) -> SinqSequence<R>
join<S: Sequence, K: Hashable>
(#inner: S, outerKey: T -> K, innerKey: S.E -> K)
-> SinqSequence<(T, S.E)>
last() -> T
lastOrNil() -> T?
last(predicate: T -> Bool) -> T
lastOrNil(predicate: T -> Bool) -> T?
lastOrDefault(defaultElement: T) -> T
lastOrDefault(defaultElement: T, predicate: T -> Bool) -> T
min<R: Comparable>(key: T -> R) -> R
max<R: Comparable>(key: T -> R) -> R
argmin<R: Comparable>(key: T -> R) -> T
argmax<R: Comparable>(key: T -> R) -> T
orderBy<K: Comparable>(key: T -> K) -> SinqOrderedSequence<T>
orderByDescending<K: Comparable>(key: T -> K) -> SinqOrderedSequence<T>
reverse() -> SinqSequence<T>
select<V>(selector: T -> V) -> SinqSequence<V>
select<V>(selector: (T, Int) -> V) -> SinqSequence<V>
selectMany<S: Sequence>(selector: T -> S) -> SinqSequence<S.E>
selectMany<S: Sequence>(selector: (T, Int) -> S) -> SinqSequence<S.E>
selectMany<S: Sequence, R>(selector: T -> S, result: S.E -> R) -> SinqSequence<R>
selectMany<S: Sequence, R>(selector: (T, Int) -> S, result: S.E -> R) -> SinqSequence<R>
single() -> T
singleOrNil() -> T?
singleOrDefault(defaultElement: T) -> T
single(predicate: T -> Bool) -> T
singleOrNil(predicate: T -> Bool) -> T?
singleOrDefault(defaultElement: T, predicate: T -> Bool) -> T
skip(count: Int) -> SinqSequence<T>
skipWhile(predicate: T -> Bool) -> SinqSequence<T>
take(count: Int) -> SinqSequence<T>
takeWhile(predicate: T -> Bool) -> SinqSequence<T>
thenBy<K: Comparable>(key: T -> K) -> SinqOrderedSequence<T>
thenByDescending<K: Comparable>(key: T -> K) -> SinqOrderedSequence<T>
toArray() -> T[]
toDictionary<K: Hashable, V>(keyValue: T -> (K, V)) -> Dictionary<K, V>
toDictionary<K: Hashable, V>(key: T -> K, value: T -> V) -> Dictionary<K, V>
toDictionary<K: Hashable>(key: T -> K) -> Dictionary<K, T>
union<S: Sequence>(sequence: S, equality: (T, T) -> Bool) -> SinqSequence<T>
union<S: Sequence, K: Hashable>(sequence: S, key: T -> K) -> SinqSequence<T>
whereTrue(predicate: T -> Bool) -> SinqSequence<T>
zip<S: Sequence, R>(sequence: S, result: (T, S.E) -> R) -> SinqSequence<R>
Due to bug in swift
it sometimes happens that swift
compiler and/or SourceKitService
loop indefinitely while solving type constraints during compilation or indexing. If this happens, to help them solve the constraints, one might have to divide work for them into smaller pieces. For example this would cause inifinite loop:
func testAll() {
XCTAssertTrue(sinq([11, 12, 15, 10]).all{ $0 >= 10 })
XCTAssertFalse(sinq([11, 12, 15, 10]).all{ $0 > 10 })
}
While this does not:
func testAll() {
let seq = sinq([11, 12, 15, 10])
XCTAssertTrue(seq.all{ $0 >= 10 })
XCTAssertFalse(seq.all{ $0 < 13 })
}
In this case it seems o be connected to @auto_closure
arguments of XCTAssert*
...
In case it happens for you, try to divide the statements like this or be more explicit in code about types and not depend as much on type inference.
¹ - this statement might become less true in the future, e.g. in Beta 4 Apple introduced lazy()
which is similar to subset of sinq()
in that it adds lazy chainable .map
.filter
.reverse
.array
and subscripting.