Latest | 0.7.0 |
---|---|

Homepage | https://github.com/slazyk/SINQ |

License | MIT |

Platforms | ios 8.0, osx 10.9, requires ARC |

Authors |

# SINQ – Swift Integrated Query

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.

## Overview

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.

## Examples

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>.*

## Installation

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`

.

## List of methods

`aggregate`

/ `reduce`

– combine all the elements of the sequence into a result

```
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`

– check if a predicate is true for all the elements

`all(predicate: T -> Bool) -> Bool`

`any`

– check if not empty, or if a predicate is true for any object

```
any() -> Bool
any(predicate: T -> Bool) -> Bool
```

`concat`

– create a sequence concatenating two sequences

`concat<S: Sequence>(other: S) -> SinqSequence<T>`

`contains`

– check if the sequence contains an element

```
contains(value: T, equality: (T, T) -> Bool) -> Bool
contains<K: Equatable>(value: T, key: T -> K) -> Bool
```

`count`

– count the elements in the sequence

`func count() -> Int`

`distinct`

– create a sequence with unique elements, in order

```
distinct(equality: (T, T) -> Bool) -> SinqSequence<T>
distinct<K: Hashable>(key: T -> K) -> SinqSequence<T>
```

`each`

– iterate over the sequence

`each(function: T -> ()) -> () `

`elementAt`

– get element at given index from the sequence

```
elementAtOrNil(index: Int) -> T?
elementAt(index: Int) -> T
elementAt(index: Int, orDefault: T) -> T
```

`except`

– create sequence with unique elements, excluding given

```
except<S: Sequence>(sequence: S, equality: (T, T) -> Bool) -> SinqSequence<T>
except<S: Sequence, K: Hashable>(sequence: S, key: T -> K) -> SinqSequence<T>
```

`first`

– get first element of the sequence [satisfying a predicate]

```
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`

– create a sequence grouping elements by given key

```
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`

– create a sequence joining two sequences with grouping

```
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`

– create a sequence with unique elements present in both sequences

```
intersect<S: Sequence>(sequence: S, equality: (T, T) -> Bool) -> SinqSequence<T>
intersect<S: Sequence, K: Hashable>(sequence: S, key: T -> K) -> SinqSequence<T>
```

`join`

– create a sequence joining two sequences without grouping

```
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`

– return last element in the sequence [satisfying a predicate]

```
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`

/ `max`

– return the minimum/maximum value of a function for the sequence

```
min<R: Comparable>(key: T -> R) -> R
max<R: Comparable>(key: T -> R) -> R
```

`argmin`

/ `argmax`

– return the element for which the function has the minimum/maximum value

```
argmin<R: Comparable>(key: T -> R) -> T
argmax<R: Comparable>(key: T -> R) -> T
```

`orderBy`

/ `orderByDescending`

– create a sequence sorted by given key

```
orderBy<K: Comparable>(key: T -> K) -> SinqOrderedSequence<T>
orderByDescending<K: Comparable>(key: T -> K) -> SinqOrderedSequence<T>
```

`reverse`

– create a sequence with reverse order

`reverse() -> SinqSequence<T>`

`select`

/ `map`

– create a sequence with results of applying given function

```
select<V>(selector: T -> V) -> SinqSequence<V>
select<V>(selector: (T, Int) -> V) -> SinqSequence<V>
```

`selectMany`

– create a sequence by concatenating function results for each element

```
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`

– return the only element in a sequence containing exactly one element

```
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`

– create a sequence skipping (given number of elements | while predicate holds )

```
skip(count: Int) -> SinqSequence<T>
skipWhile(predicate: T -> Bool) -> SinqSequence<T>
```

`take`

– create a sequence by taking (given number of elements | while predicate holds )

```
take(count: Int) -> SinqSequence<T>
takeWhile(predicate: T -> Bool) -> SinqSequence<T>
```

`thenBy`

/ `thenByDescending`

– create a sequence by additionally sorting on given key

```
thenBy<K: Comparable>(key: T -> K) -> SinqOrderedSequence<T>
thenByDescending<K: Comparable>(key: T -> K) -> SinqOrderedSequence<T>
```

`toArray`

– create an array from the sequence

`toArray() -> T[]`

`toDictionary`

/ `toLookupDictionary`

– create a dictionary from the sequence

```
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`

– create a sequence with unique elements from either of the sequences

```
union<S: Sequence>(sequence: S, equality: (T, T) -> Bool) -> SinqSequence<T>
union<S: Sequence, K: Hashable>(sequence: S, key: T -> K) -> SinqSequence<T>
```

`whereTrue`

/ `filter`

– create a sequence only with elements satisfying a predicate

`whereTrue(predicate: T -> Bool) -> SinqSequence<T>`

`zip`

– create a sequence by combining pairs of elements from two sequences

`zip<S: Sequence, R>(sequence: S, result: (T, S.E) -> R) -> SinqSequence<R>`

## Troubleshooting

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.

### Latest podspec

{ "name": "SINQ", "module_name": "SINQ", "version": "0.7.0", "license": "MIT", "platforms": { "ios": "8.0", "osx": "10.9" }, "homepage": "https://github.com/slazyk/SINQ", "authors": { "Leszek Slazynski": "[email protected]" }, "social_media_url": "https://twitter.com/slazyk", "summary": "LINQ for Swift - Swift Integrated Query", "source": { "git": "https://github.com/slazyk/SINQ.git", "tag": "v0.7.0" }, "source_files": "SINQ/*.swift", "requires_arc": true, "pushed_with_swift_version": "3.0" }

Wed, 29 Mar 2017 07:40:03 +0000