SwiftSequence
A μframework of extensions for SequenceType in Swift 2.0, inspired by Python's itertools, Haskell's standard library, and other things.
Install / Use
/learn @oisdk/SwiftSequenceREADME
SwiftSequence
Full reference here.
(If you're looking for data structures in Swift, those have been moved to here)
SwiftSequence is a lightweight framework of extensions to SequenceType. It has no requirements beyond the Swift standard library. Every function and method has both a strict and a lazy version, unless otherwise specified.
To make an eager sequence lazy, the lazy property can be used:
[1, 2, 3].lazy.hop(2)
// 1, 3
Contributing
Contributions welcome! Anything at all, as long as it's related to sequences, Linux-compatible, and has a test or two.
Contents
- [ScanReduce] (#scanreduce)
- [TakeDrop] (#takedrop)
- [Hopping and Slicing] (#hopping-and-slicing)
- [Interpose] (#interpose)
- [Combinations] (#combinations)
- [Permutations] (#permutations)
- [Cycle] (#cycle)
- [Categorise] (#categorise)
- [ChunkWindowSplit] (#chunkwindowsplit)
- [Enumerate] (#enumerate)
- [Finding] (#finding)
- [NestedSequences] (#nestedsequences)
- [Zip] (#zip)
ScanReduce
Reduce
func reduce
(@noescape combine: (accumulator: Generator.Element, element: Generator.Element) throws -> Generator.Element)
rethrows -> Generator.Element?
This function works the same as the standard library reduce, except it takes the initial value to be the first element of self. It returns an optional, which is nil if self is empty.
[1, 2, 3, 4].reduce(+) == 10
Scan
Returns an array of the intermediate results of calling combine on successive elements of self and an accumulator:
[1, 2, 3].scan(0, combine: +)
[1, 3, 6]
The last element of this array will be equal to the result of calling reduce with the same arguments.
There is also, like reduce, a version that takes the first element of the sequence to be initial:
[1, 2, 3, 4, 5].scan(+)
[3, 6, 10, 15]
This also is evaluated lazily if the sequence it is called on is lazy.
TakeDrop
prefixWhile
This function returns all of the elements of self up until an element returns false for predicate:
[1, 2, 3, 4, 5, 2].prefixWhile { $0 < 4 }
[1, 2, 3]
Note that it's not the same as filter: if any elements return true for the predicate after the first element that returns false, they're still not returned.
dropWhile
Similar in behaviour to prefixWhile, this function drops the first elements of self that return true for a predicate:
lazy([1, 2, 3, 4, 5, 2]).dropWhile { $0 < 4 }
4, 5, 2
breakAt
Returns a tuple, the first element being the prefix of self of maximum length n, the second being the remaining elements of self.
[1, 2, 3, 4].breakAt(3) == ([1, 2, 3], [4])
This also has a version which takes a predicate, which returns a tuple where the first element is the prefix of self up until the first element that returns true for isBreak.
Hopping and Slicing
This allows subscripting of several CollectionTypes in a way similar to Python's slicing. For instance, you can slice in an open-ended way:
let array = [0, 1, 2, 3, 4, 5, 6]
array[3...] == [3, 4, 5, 6]
array[...4] == [0, 1, 2, 3, 4]
array[..<4] == [0, 1, 2, 3]
You can also mutate via slices:
var array = [0, 1, 2, 3, 4, 5, 6]
array[...3] = [10, 11, 12, 13]
array == [10, 11, 12, 13, 4, 5, 6]
Or, you can hop over elements in the array:
let array = [0, 1, 2, 3, 4, 5, 6]
array[2..., by: 2] // [2, 4, 6]
Interpose
These functions allow lazy and eager insertion of elements into sequences at regular intervals.
Interpose
This function returns self, with element inserted between every element:
[1, 2, 3].interpose(10)
[1, 10, 2, 10, 3]
The intervals at which to insert element can be specified:
[1, 2, 3, 4, 5].interpose(10, n: 2)
[1, 2, 10, 3, 4, 10, 5]
More than one element can be interposed:
[1, 2, 3].interpose([10, 20])
[1, 10, 20, 2, 10, 20, 3]
And, again, the interval can be specified:
[1, 2, 3, 4, 5].interpose([10, 20], n: 2)
[1, 2, 10, 20, 3, 4, 10, 20, 5]
interdig
This function allows you to combine two sequences by alternately selecting elements from each:
interdig([1, 2, 3], [10, 20, 30])
[1, 10, 2, 20, 3, 30]
The length of the interdigitations can be specified:
interdig([1, 2, 3, 4, 5], [10, 20, 30, 40, 50, 60], s0Len: 2, s1Len: 3)
[1, 2, 10, 20, 30, 3, 4, 40, 50, 60, 5]
Combinations
These functions return combinations with or without repetition, lazily or eagerly. The lazy version of these function doesn't maintain laziness of the underlying sequence, but they produce combinations on-demand, with neither future nor past combinations stored in memory, e.g:
let lazySequence = [1, 2, 3].lazy
let lazyCombos = lazySequence.lazyCombinations(2)
// Here, lazySequence was evaluated, but no combinations have yet been evaluated.
var g = lazyCombos.generate()
g.next() == [1, 2]
// Here, only one combination has been evaluated, and only that combination is stored in memory
g.next() == [1, 3]
// Here, two combinations have been evaluated, but no extra combinations have been stored in memory.
Combinations
Returns combinations without repetitions of self of length n
Combinations are returned in lexicographical order, according to the order of self
[1, 2, 3].combinations(2)
[1, 2], [1, 3], [2, 3]
To have combinations generate lazily and on-demand, use lazyCombinations().
Example Recipe:
extension CollectionType {
func powerSet() -> FlatMapSeq<LazyForwardCollection<Self>, ComboSeq<[Self.Generator.Element]>> {
var i = 0
return lazy(self).flatMap { _ in self.lazyCombinations(++i) }
}
}
[1, 2, 3] // [1], [2], [3], [1, 2], [1, 3], [2, 3], [1, 2, 3]
.powerSet()
Combinations with Repetition
Returns combinations with repetition of self of length n.
Combinations are returned in lexicographical order, according to the order of self.
[1, 2, 3].combinationsWithRep(2)
[1, 1], [1, 2], [1, 3], [2, 2], [2, 3], [3, 3]
To have combinations generate lazily and on-demand, use lazyCombinationsWithRep().
Permutations
Lexicographical Permutations
These functions return self, permuted, in lexicographical order. If self is not the first permutation, lexicographically, not all permutations will be returned. (to ensure all permutations are returned, sort() can be used). This function can operate on a collection of Comparable elements, or, is the closure isOrderedBefore is provided, it can operate on any collection. In terms of laziness, it behaves the same as the combination functions: forcing the evaluation of the underlying collection, but capable of lazily producing each new permutation. To access the lazy version, use the versions of these functions with the lazy prefix. (e.g., lexPermutations() becomes lazyLexPermutations())
[1, 2, 3].lexPermutations()
[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
[3, 2, 1].lexPermutations()
[[3, 2, 1]]
[1, 2, 3].lexPermutations(<)
[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
[1, 2, 3].lexPermutations(>)
[[1, 2, 3]]
Permutations
These functions use the same algorithm as the lexicographical permutations, but the indices of self are permuted. (Indices aren't returned: they're just used for the permutation)
[1, 2, 3].permutations()
[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]
lazy([3, 2, 1]).lazyPermutations()
[3, 2, 1], [3, 1, 2], [2, 3, 1], [2, 1, 3], [1, 3, 2], [1, 2, 3]
Cycle
These functions return a cycle of self. The number of cycles can be specified, if not, self is cycled infinitely.
When called on a LazySequenceType, the sequence returned is lazy, otherwise, it's eager. (the infinite cycle is always lazy, however)
[1, 2, 3].cycle(2)
[1, 2, 3, 1, 2, 3]
[1, 2, 3].cycle()
1, 2, 3, 1, 2, 3, 1...
Categorise
These functions can be used to group elements of a sequence on certain conditions.
categorise
func categorise<U : Hashable>(keyFunc: Generator.Element -> U) -> [U:[Generator.Element]]
This categorises all elements of self into a dictionary, with the keys of that dictionary given by the keyFunc.
[1, 2, 3, 4, 5, 6].categorise { $0 % 2 }
[
0: [2, 4, 6],
1: [1, 3, 5]
]
(this function has no lazy version)
Frequencies
This returns a dictionary where the keys are the elements of self, and the values are their respective frequencies:
[1, 1, 1, 2, 2, 3].freqs()
[
1: 3,
2: 2,
3: 1
]
(this function has no lazy version)
Uniques
Returns self with duplicates removed:
[1, 2, 3, 2, 2, 4, 1, 2, 5, 6].uniques()
[1, 2, 3, 4, 5, 6]
Grouping
Since categorise() and freqs() can't have lazy versions, these grouping functions give similar behaviour. Instead of categorising based on the whole sequence, they categories based on adjacent values.
This groups adjacent equal values:
lazy([1, 2, 2, 3, 1, 1, 3, 4, 2]).group()
[1], [2, 2], [3], [1, 1], [3], [4], [2]
This groups adjacent equal values according to a closure:
lazy([1, 3, 5, 20, 22, 18, 6, 7]).group { abs($0 - $1) < 5 }
[1, 3, 5], [20, 22, 18], [6, 7]
This groups adjacent values that return the same from
