Skip to content

Commit

Permalink
All permutations (#56)
Browse files Browse the repository at this point in the history
Adds the ability to easily get permutations of a range of sizes, like #51 did for Combinations.
  • Loading branch information
mdznr authored Feb 12, 2021
1 parent d2a1ca5 commit d8a11e0
Show file tree
Hide file tree
Showing 3 changed files with 320 additions and 57 deletions.
25 changes: 25 additions & 0 deletions Guides/Permutations.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,31 @@ for perm in numbers2.permutations() {
// [10, 10, 20]
```

Given a range, the `permutations(ofCount:)` method returns a sequence of all the different permutations of the given sizes of a collection’s elements in increasing order of size.

```swift
let numbers = [10, 20, 30]
for perm in numbers.permutations(ofCount: 0...) {
print(perm)
}
// []
// [10]
// [20]
// [30]
// [10, 20]
// [10, 30]
// [20, 10]
// [20, 30]
// [30, 10]
// [30, 20]
// [10, 20, 30]
// [10, 30, 20]
// [20, 10, 30]
// [20, 30, 10]
// [30, 10, 20]
// [30, 20, 10]
```

## Detailed Design

The `permutations(ofCount:)` method is declared as a `Collection` extension,
Expand Down
220 changes: 165 additions & 55 deletions Sources/Algorithms/Permutations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,62 +11,93 @@

/// A sequence of all the permutations of a collection's elements.
public struct Permutations<Base: Collection> {
/// The base collection.
/// The base collection to iterate over for permutations.
public let base: Base

@usableFromInline
internal let baseCount: Int
internal let countToChoose: Int

/// The range of accepted sizes of permutations.
/// - Note: This may be empty if the attempted range entirely exceeds the
/// bounds of the size of the `base` collection.
@usableFromInline
internal let kRange: Range<Int>

/// Initializes a `Permutations` for all permutations of `base` of size `k`.
/// - Parameters:
/// - base: The collection to iterate over for permutations
/// - k: The expected size of each permutation, or `nil` (default) to
/// iterate over all permutations of the same size as the base collection.
@usableFromInline
internal init(_ base: Base, k: Int? = nil) {
let kRange: ClosedRange<Int>?
if let countToChoose = k {
kRange = countToChoose ... countToChoose
} else {
kRange = nil
}
self.init(base, kRange: kRange)
}

/// Initializes a `Permutations` for all combinations of `base` of sizes
/// within a given range.
/// - Parameters:
/// - base: The collection to iterate over for permutations.
/// - kRange: The range of accepted sizes of permutations, or `nil` to
/// iterate over all permutations of the same size as the base collection.
@usableFromInline
internal init<R: RangeExpression>(
_ base: Base, kRange: R?
) where R.Bound == Int {
self.base = base
let baseCount = base.count
self.baseCount = baseCount
self.countToChoose = k ?? baseCount
let upperBound = baseCount + 1
self.kRange = kRange?.relative(to: 0 ..< .max)
.clamped(to: 0 ..< upperBound) ??
baseCount ..< upperBound
}

/// The total number of permutations.
@inlinable
public var count: Int {
return baseCount >= countToChoose
? stride(from: baseCount, to: baseCount - countToChoose, by: -1).reduce(1, *)
: 0
return kRange.map {
stride(from: baseCount, to: baseCount - $0, by: -1).reduce(1, *)
}.reduce(0, +)
}
}

extension Permutations: Sequence {
/// The iterator for a `Permutations` instance.
public struct Iterator: IteratorProtocol {
@usableFromInline
internal var base: Base

@usableFromInline
internal var indexes: [Base.Index]
@usableFromInline
internal var hasMorePermutations: Bool
internal let baseCount: Int

/// The current range of accepted sizes of permutations.
/// - Note: The range is contracted until empty while iterating over
/// permutations of different sizes. When the range is empty, iteration is
/// finished.
@usableFromInline
internal var countToChoose: Int = 0
/// `true` if we're generating permutations of the full collection.
internal var kRange: Range<Int>

/// Whether or not iteration is finished (`kRange` is empty)
@usableFromInline
internal var permutesFullCollection: Bool {
countToChoose == indexes.count
internal var isFinished: Bool {
return kRange.isEmpty
}

@usableFromInline
internal init(_ base: Base) {
self.base = base
self.indexes = Array(base.indices)
self.countToChoose = self.indexes.count
self.hasMorePermutations = true
}
internal var indexes: [Base.Index]

@usableFromInline
internal init(_ base: Base, count: Int) {
self.base = base
self.countToChoose = count

// Produce exactly one empty permutation when `count == 0`.
self.indexes = count == 0 ? [] : Array(base.indices)

// Can't produce any permutations when `count > base.count`.
self.hasMorePermutations = count <= indexes.count
internal init(_ permutations: Permutations) {
self.base = permutations.base
self.baseCount = permutations.baseCount
self.kRange = permutations.kRange
self.indexes = Array(permutations.base.indices)
}

/// Advances the `indexes` array such that the first `countToChoose`
Expand All @@ -82,72 +113,92 @@ extension Permutations: Sequence {
/// - Complexity: O(*n*), where *n* is the length of the collection.
@usableFromInline
internal mutating func nextState() -> Bool {
let countToChoose = self.kRange.lowerBound
let edge = countToChoose - 1

// Find first index greater than the one at `edge`.
if let i = indexes[countToChoose...].firstIndex(where: { indexes[edge] < $0 }) {
indexes.swapAt(edge, i)
} else {
indexes.reverse(subrange: countToChoose..<indexes.endIndex)

indexes.reverse(subrange: countToChoose ..< indexes.endIndex)
// Find last increasing pair below `edge`.
// TODO: This could be indexes[..<edge].adjacentPairs().lastIndex(where: ...)
var lastAscent = edge - 1
while (lastAscent >= 0 && indexes[lastAscent] >= indexes[lastAscent + 1]) {
lastAscent -= 1
}
if (lastAscent < 0) {
if lastAscent < 0 {
return false
}

// Find rightmost index less than that at `lastAscent`.
if let i = indexes[lastAscent...].lastIndex(where: { indexes[lastAscent] < $0 }) {
indexes.swapAt(lastAscent, i)
}
indexes.reverse(subrange: (lastAscent + 1)..<indexes.endIndex)
indexes.reverse(subrange: (lastAscent + 1) ..< indexes.endIndex)
}

return true
}

@inlinable
public mutating func next() -> [Base.Element]? {
if !hasMorePermutations { return nil }
guard !isFinished else { return nil }

/// Advances `kRange` by incrementing its `lowerBound` until the range is
/// empty, when iteration is finished.
func advanceKRange() {
kRange.removeFirst()
indexes = Array(base.indices)
}

let countToChoose = self.kRange.lowerBound
if countToChoose == 0 {
defer {
advanceKRange()
}
return []
}

let permutesFullCollection = (countToChoose == baseCount)
if permutesFullCollection {
// If we're permuting the full collection, each iteration is just a
// call to `nextPermutation` on `indexes`.
defer { hasMorePermutations = indexes.nextPermutation() }
defer {
let hasMorePermutations = indexes.nextPermutation()
if !hasMorePermutations {
advanceKRange()
}
}
return indexes.map { base[$0] }
} else {
// Otherwise, return the items at the first `countToChoose` indices and
// advance the state.
defer { hasMorePermutations = nextState() }
defer {
let hasMorePermutations = nextState()
if !hasMorePermutations {
advanceKRange()
}
}
return indexes.prefix(countToChoose).map { base[$0] }
}
}
}

@usableFromInline
internal var permutesFullCollection: Bool {
baseCount == countToChoose
}

public func makeIterator() -> Iterator {
permutesFullCollection
? Iterator(base)
: Iterator(base, count: countToChoose)
Iterator(self)
}
}

extension Permutations: LazySequenceProtocol where Base: LazySequenceProtocol {}

//===----------------------------------------------------------------------===//
// nextPermutation(by:)
// nextPermutation()
//===----------------------------------------------------------------------===//

extension MutableCollection
where Self: BidirectionalCollection, Element: Comparable
where Self: BidirectionalCollection, Element: Comparable
{
/// Permutes this collection's elements through all the lexical orderings.
///
Expand All @@ -163,8 +214,8 @@ extension MutableCollection
/// - Complexity: O(*n*), where *n* is the length of the collection.
@usableFromInline
internal mutating func nextPermutation() -> Bool {
// ensure we have > 1 element in the collection
if isEmpty { return false }
// Ensure we have > 1 element in the collection.
guard !isEmpty else { return false }
var i = index(before: endIndex)
if i == startIndex { return false }

Expand All @@ -178,7 +229,7 @@ extension MutableCollection
formIndex(before: &j)
}
swapAt(i, j)
self.reverse(subrange: ip1..<endIndex)
self.reverse(subrange: ip1 ..< endIndex)
return true
}

Expand All @@ -195,6 +246,62 @@ extension MutableCollection
//===----------------------------------------------------------------------===//

extension Collection {
/// Returns a collection of the permutations of this collection with lengths
/// in the specified range.
///
/// This example prints the different permutations of one to two elements from
/// an array of three names:
///
/// let names = ["Alex", "Celeste", "Davide"]
/// for perm in names.permutations(ofCount: 1...2) {
/// print(perm.joined(separator: ", "))
/// }
/// // Alex
/// // Celeste
/// // Davide
/// // Alex, Celeste
/// // Alex, Davide
/// // Celeste, Alex
/// // Celeste, Davide
/// // Davide, Alex
/// // Davide, Celeste
///
/// This example prints _all_ the permutations (including an empty array) from
/// the an array of numbers:
///
/// let numbers = [10, 20, 30]
/// for perm in numbers.permutations(ofCount: 0...) {
/// print(perm)
/// }
/// // []
/// // [10]
/// // [20]
/// // [30]
/// // [10, 20]
/// // [10, 30]
/// // [20, 10]
/// // [20, 30]
/// // [30, 10]
/// // [30, 20]
/// // [10, 20, 30]
/// // [10, 30, 20]
/// // [20, 10, 30]
/// // [20, 30, 10]
/// // [30, 10, 20]
/// // [30, 20, 10]
///
/// - Parameter kRange: The number of elements to include in each permutation.
///
/// - Complexity: O(1) for random-access base collections. O(*n*) where *n*
/// is the number of elements in the base collection, since `Permutations`
/// accesses the `count` of the base collection.
@inlinable
public func permutations<R: RangeExpression>(
ofCount kRange: R
) -> Permutations<Self> where R.Bound == Int {
return Permutations(self, kRange: kRange)
}

/// Returns a collection of the permutations of this collection of the
/// specified length.
///
Expand Down Expand Up @@ -237,10 +344,13 @@ extension Collection {
/// sequence, the resulting sequence has no elements.
///
/// - Parameter k: The number of elements to include in each permutation.
/// If `count` is `nil`, the resulting sequence represents permutations
/// of this entire collection.
/// If `k` is `nil`, the resulting sequence represents permutations of this
/// entire collection.
///
/// - Complexity: O(1)
/// - Complexity: O(1) for random-access base collections. O(*n*) where *n*
/// is the number of elements in the base collection, since `Permutations`
/// accesses the `count` of the base collection.
@inlinable
public func permutations(ofCount k: Int? = nil) -> Permutations<Self> {
precondition(
k ?? 0 >= 0,
Expand Down
Loading

0 comments on commit d8a11e0

Please sign in to comment.