An open API service indexing awesome lists of open source software.

https://github.com/slazyk/SINQ

LINQ for Swift - Swift Integrated Query
https://github.com/slazyk/SINQ

Last synced: 4 days ago
JSON representation

LINQ for Swift - Swift Integrated Query

Awesome Lists containing this project

README

          

# 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](https://twitter.com/slazyk) and [github](https://github.com/slazyk). Be sure to check out [Observable-Swift](https://github.com/slazyk/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:

```swift
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` 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` 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` or `SSequence`.*

## Installation

You can use either [CocoaPods](https://cocoapods.org/) or [Carthage](https://github.com/Carthage/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
```swift
aggregate(combine: (T, T) -> T) -> T
aggregate(initial: R, combine: (R, T) -> R) -> R
aggregate(initial: C, combine: (C, T) -> C, result: C -> R) -> R
```
##### `all` - check if a predicate is true for all the elements
```swift
all(predicate: T -> Bool) -> Bool
```
##### `any` - check if not empty, or if a predicate is true for any object
```swift
any() -> Bool
any(predicate: T -> Bool) -> Bool
```
##### `concat` - create a sequence concatenating two sequences
```swift
concat(other: S) -> SinqSequence
```
##### `contains` - check if the sequence contains an element
```swift
contains(value: T, equality: (T, T) -> Bool) -> Bool
contains(value: T, key: T -> K) -> Bool
```
##### `count` - count the elements in the sequence
```swift
func count() -> Int
```
##### `distinct` - create a sequence with unique elements, in order
```swift
distinct(equality: (T, T) -> Bool) -> SinqSequence
distinct(key: T -> K) -> SinqSequence
```
##### `each` - iterate over the sequence
```swift
each(function: T -> ()) -> ()
```
##### `elementAt` - get element at given index from the sequence
```swift
elementAtOrNil(index: Int) -> T?
elementAt(index: Int) -> T
elementAt(index: Int, orDefault: T) -> T
```
##### `except` - create sequence with unique elements, excluding given
```swift
except(sequence: S, equality: (T, T) -> Bool) -> SinqSequence
except(sequence: S, key: T -> K) -> SinqSequence
```
##### `first` - get first element of the sequence [satisfying a predicate]
```swift
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
```swift
groupBy(key: T -> K) -> SinqSequence>
groupBy(key: T -> K, element: T -> V) -> SinqSequence>
groupBy(key: T -> K, element: T -> V, result: (K, SinqSequence) -> R) -> SinqSequence
```
##### `groupJoin` - create a sequence joining two sequences with grouping
```swift
groupJoin
(#inner: S, outerKey: T -> K, innerKey: S.E -> K)
-> SinqSequence>
groupJoin
(#inner: S, outerKey: T -> K, innerKey: S.E -> K,
result: (T, SinqSequence) -> R) -> SinqSequence
```
##### `intersect` - create a sequence with unique elements present in both sequences
```swift
intersect(sequence: S, equality: (T, T) -> Bool) -> SinqSequence
intersect(sequence: S, key: T -> K) -> SinqSequence
```
##### `join` - create a sequence joining two sequences without grouping
```swift
join
(#inner: S, outerKey: T -> K, innerKey: S.E -> K,
result: (T, S.E) -> R) -> SinqSequence
join
(#inner: S, outerKey: T -> K, innerKey: S.E -> K)
-> SinqSequence<(T, S.E)>
```
##### `last` - return last element in the sequence [satisfying a predicate]
```swift
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
```swift
min(key: T -> R) -> R
max(key: T -> R) -> R
```
##### `argmin` / `argmax` - return the element for which the function has the minimum/maximum value
```swift
argmin(key: T -> R) -> T
argmax(key: T -> R) -> T
```
##### `orderBy` / `orderByDescending` - create a sequence sorted by given key
```swift
orderBy(key: T -> K) -> SinqOrderedSequence
orderByDescending(key: T -> K) -> SinqOrderedSequence
```
##### `reverse` - create a sequence with reverse order
```swift
reverse() -> SinqSequence
```
##### `select` / `map` - create a sequence with results of applying given function
```swift
select(selector: T -> V) -> SinqSequence
select(selector: (T, Int) -> V) -> SinqSequence
```
##### `selectMany` - create a sequence by concatenating function results for each element
```swift
selectMany(selector: T -> S) -> SinqSequence
selectMany(selector: (T, Int) -> S) -> SinqSequence
selectMany(selector: T -> S, result: S.E -> R) -> SinqSequence
selectMany(selector: (T, Int) -> S, result: S.E -> R) -> SinqSequence
```
##### `single` - return the only element in a sequence containing exactly one element
```swift
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 )
```swift
skip(count: Int) -> SinqSequence
skipWhile(predicate: T -> Bool) -> SinqSequence
```
##### `take` - create a sequence by taking (given number of elements | while predicate holds )
```swift
take(count: Int) -> SinqSequence
takeWhile(predicate: T -> Bool) -> SinqSequence
```
##### `thenBy` / `thenByDescending` - create a sequence by additionally sorting on given key
```swift
thenBy(key: T -> K) -> SinqOrderedSequence
thenByDescending(key: T -> K) -> SinqOrderedSequence
```
##### `toArray` - create an array from the sequence
```swift
toArray() -> T[]
```
##### `toDictionary` / `toLookupDictionary` - create a dictionary from the sequence
```swift
toDictionary(keyValue: T -> (K, V)) -> Dictionary
toDictionary(key: T -> K, value: T -> V) -> Dictionary
toDictionary(key: T -> K) -> Dictionary
```
##### `union` - create a sequence with unique elements from either of the sequences
```swift
union(sequence: S, equality: (T, T) -> Bool) -> SinqSequence
union(sequence: S, key: T -> K) -> SinqSequence
```
##### `whereTrue` / `filter` - create a sequence only with elements satisfying a predicate
```swift
whereTrue(predicate: T -> Bool) -> SinqSequence
```
##### `zip` - create a sequence by combining pairs of elements from two sequences
```swift
zip(sequence: S, result: (T, S.E) -> R) -> SinqSequence
```

## 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:
```swift
func testAll() {
XCTAssertTrue(sinq([11, 12, 15, 10]).all{ $0 >= 10 })
XCTAssertFalse(sinq([11, 12, 15, 10]).all{ $0 > 10 })
}
```
While this does not:
```swift
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.