A μframework of extensions for SequenceType in Swift 2.0, inspired by Python's itertools, Haskell's standard library, and other things.
Swift Other
Latest commit ad3dfc9 Jun 15, 2016 @oisdk committed on GitHub Merge pull request #8 from regexident/universal
Simplified universal framework config. Without depedencies.

README.md

Build Status Carthage compatible

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

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 keyFunc:

lazy([1, 3, 5, 2, 4, 6, 6, 7, 1, 1]).groupBy { $0 % 2 }

[1, 3, 5], [2, 4, 6, 6], [7, 1, 1]

ChunkWindowSplit

These functions divide up a sequence.

Chunk

This function returns self, broken up into non-overlapping arrays of length n:

[1, 2, 3, 4, 5].chunk(2)

[[1, 2], [3, 4], [5]]

Window

This function returns self, broken up into overlapping arrays of length n:

[1, 2, 3, 4, 5].window(3)

[[1, 2, 3], [2, 3, 4], [3, 4, 5]]

Enumerate

This just adds the function specEnumerate() which is the same as the standard library enumerate(), except that the indices it returns are specific to the base, rater than just Ints. So, for instance, this:

"hello".characters.specEnumerate()

Would return a sequence of (String.Index, Character) (rather than (Int, Character), which is what the standard library enumerate() returns). There is no eager version of this function (as there is no eager enumerate())

Finding

The methods here are intended to replace patterns like this:

[1, 2, 3, 4, 5, 6].filter { n in n > 4 }.first
[1, 2, 3, 4, 5, 6].filter { n in n % 2 == 0 }.count

Where filter is used in a chain with one of the CollectionType properties, like first or count.

These chains are needlessly inefficient: they allocate and fill an array unnecessarily. Even if the lazy property is used, the actual operations are unexpected. The code below, for instance:

var i = 0

[1, 2, 3, 4, 5, 6]
  .lazy
  .filter { n in
    print(++i)
    return n > 4
}.first

Will print one through ten.

first, last, and count methods are all provided, which take predicates, as well as indicesOf and lastIndexOf.

A partition method is also available, which splits self into a tuple of arrays which satisfy and do not satisfy predicate, respectively.

NestedSequences

Transpose

Allows both lazy and eager transposition. When lazily transposing, each row is evaluated eagerly, but only that row:

let transposed = lazy([
  [1, 2, 3],
  [1, 2, 3],
  [1, 2, 3]
]).transpose()

var g = transposed.generate()

g.next() == [1, 1, 1]

// Each row is an eager array, but only that row is evaluated.

g.next() == [2, 2, 2]

// It's safe to use with single-pass sequences, also, as each sequence is only evaluated once.

Product

Both lazy and eager Cartesian Products.

product([1, 2], [3, 4])

[[1, 3], [1, 4], [2, 3], [2, 4]]

lazyProduct([1, 2], [3, 4])

[1, 3], [1, 4], [2, 3], [2, 4]

Zip

These functions allow you to zip two sequences of different lengths together, and to specify the padding for the shorter sequence. If unspecified, the padding is nil. There is no eager version of this function (there is no eager standard library zip)

zipWithPadding([1, 2, 3], [1, 2], pad0: 100, pad1: 900)

(1, 1), (2, 2), (3, 900)
zipWithPadding([1, 2, 3], [1, 2])

(1?, 1?), (2?, 2?), (3?, nil)