diff --git a/Guides/Permutations.md b/Guides/Permutations.md index 018135a7..7c1d7595 100644 --- a/Guides/Permutations.md +++ b/Guides/Permutations.md @@ -3,7 +3,7 @@ [[Source](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/Permutations.swift) | [Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/PermutationsTests.swift)] -A type that computes permutations of a collection’s elements, or of a subset of +Methods that compute permutations of a collection’s elements, or of a subset of those elements. The `permutations(ofCount:)` method, when called without the `ofCount` @@ -60,7 +60,18 @@ 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. +To generate only unique permutations, use the `uniquePermutations(ofCount:)` method: + +```swift +for perm in numbers2.uniquePermutations() { + print(perm) +} +// [20, 10, 10] +// [10, 20, 10] +// [10, 10, 20] +``` + +Given a range, the methods return 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] @@ -87,21 +98,29 @@ for perm in numbers.permutations(ofCount: 0...) { ## Detailed Design -The `permutations(ofCount:)` method is declared as a `Collection` extension, -and returns a `Permutations` type: +The `permutations(ofCount:)` and `uniquePermutations(ofCount:)` methods are declared as `Collection` extensions, +and return `Permutations` and `UniquePermutations` instances, respectively: ```swift extension Collection { public func permutations(ofCount k: Int? = nil) -> Permutations + public func permutations(ofCount kRange: R) -> Permutations + where R: RangeExpression, R.Bound == Int +} + +extension Collection where Element: Hashable { + public func uniquePermutations(ofCount k: Int? = nil) -> UniquePermutations + public func uniquePermutations(ofCount kRange: R) -> UniquePermutations + where R: RangeExpression, R.Bound == Int } ``` -Since the `Permutations` type needs to store an array of the collection’s -indices and mutate the array to generate each permutation, `Permutations` only -has `Sequence` conformance. Adding `Collection` conformance would require +Since both result types need to store an array of the collection’s +indices and mutate the array to generate each permutation, they only +have `Sequence` conformance. Adding `Collection` conformance would require storing the array in the index type, which would in turn lead to copying the -array at every index advancement. `Combinations` does conform to -`LazySequenceProtocol` when the base type conforms. +array at every index advancement. The `Permutations` type +conforms to `LazySequenceProtocol` when its base type conforms. ### Complexity @@ -109,6 +128,10 @@ Calling `permutations()` is an O(1) operation. Creating the iterator for a `Permutations` instance and each call to `Permutations.Iterator.next()` is an O(_n_) operation. +Calling `uniquePermutations()` is an O(_n_) operation, because it preprocesses the +collection to find duplicate elements. Creating the iterator for and each call to +`next()` is also an O(_n_) operation. + ### Naming See the ["Naming" section for `combinations(ofCount:)`](Combinations.md#naming) for detail. @@ -117,9 +140,8 @@ See the ["Naming" section for `combinations(ofCount:)`](Combinations.md#naming) **C++:** The `` library defines a `next_permutation` function that advances an array of comparable values through their lexicographic orderings. -This function is tricky to use and understand, so while it’s included in -`swift-algorithms` as an implementation detail of the `Permutations` type, it -isn’t public. +This function is very similar to the `uniquePermutations(ofCount:)` method. **Rust/Ruby/Python:** Rust, Ruby, and Python all define functions with -essentially the same semantics as the method described here. +essentially the same semantics as the `permutations(ofCount:)` method +described here. diff --git a/README.md b/README.md index 5b4d7805..638ba8cd 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Read more about the package, and the intent behind it, in the [announcement on s - [`combinations(ofCount:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Combinations.md): Combinations of particular sizes of the elements in a collection. - [`permutations(ofCount:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Permutations.md): Permutations of a particular size of the elements in a collection, or of the full collection. +- [`uniquePermutations(ofCount:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Permutations.md): Permutations of a collection's elements, skipping any duplicate permutations. #### Mutating algorithms diff --git a/Sources/Algorithms/Permutations.swift b/Sources/Algorithms/Permutations.swift index 81f8f0d8..2149494d 100644 --- a/Sources/Algorithms/Permutations.swift +++ b/Sources/Algorithms/Permutations.swift @@ -9,6 +9,72 @@ // //===----------------------------------------------------------------------===// +//===----------------------------------------------------------------------===// +// nextPermutation() +//===----------------------------------------------------------------------===// + +extension MutableCollection + where Self: BidirectionalCollection, Element: Comparable +{ + /// Permutes this collection's elements through all the lexical orderings. + /// + /// Call `nextPermutation()` repeatedly starting with the collection in + /// sorted order. When the full cycle of all permutations has been completed, + /// the collection will be back in sorted order and this method will return + /// `false`. + /// + /// - Returns: A Boolean value indicating whether the collection still has + /// remaining permutations. When this method returns `false`, the collection + /// is in ascending order according to `areInIncreasingOrder`. + /// + /// - Complexity: O(*n*), where *n* is the length of the collection. + @inlinable + internal mutating func nextPermutation(upperBound: Index? = nil) -> Bool { + // Ensure we have > 1 element in the collection. + guard !isEmpty else { return false } + var i = index(before: endIndex) + if i == startIndex { return false } + + let upperBound = upperBound ?? endIndex + + while true { + let ip1 = i + formIndex(before: &i) + + // Find the last ascending pair (ie. ..., a, b, ... where a < b) + if self[i] < self[ip1] { + // Find the last element greater than self[i] + // This is _always_ at most `ip1` due to if statement above + let j = lastIndex(where: { self[i] < $0 })! + + // At this point we have something like this: + // 0, 1, 4, 3, 2 + // ^ ^ + // i j + swapAt(i, j) + self.reverse(subrange: ip1 ..< endIndex) + + // Only return if we've made a change within .. +//===----------------------------------------------------------------------===// + /// A sequence of all the permutations of a collection's elements. public struct Permutations { /// The base collection to iterate over for permutations. @@ -194,54 +260,6 @@ extension Permutations: Sequence { extension Permutations: LazySequenceProtocol where Base: LazySequenceProtocol {} -//===----------------------------------------------------------------------===// -// nextPermutation() -//===----------------------------------------------------------------------===// - -extension MutableCollection -where Self: BidirectionalCollection, Element: Comparable -{ - /// Permutes this collection's elements through all the lexical orderings. - /// - /// Call `nextPermutation()` repeatedly starting with the collection in - /// sorted order. When the full cycle of all permutations has been completed, - /// the collection will be back in sorted order and this method will return - /// `false`. - /// - /// - Returns: A Boolean value indicating whether the collection still has - /// remaining permutations. When this method returns `false`, the collection - /// is in ascending order according to `areInIncreasingOrder`. - /// - /// - 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. - guard !isEmpty else { return false } - var i = index(before: endIndex) - if i == startIndex { return false } - - while true { - let ip1 = i - formIndex(before: &i) - - if self[i] < self[ip1] { - var j = index(before: endIndex) - while self[i] >= self[j] { - formIndex(before: &j) - } - swapAt(i, j) - self.reverse(subrange: ip1 ..< endIndex) - return true - } - - if i == startIndex { - self.reverse() - return false - } - } - } -} - //===----------------------------------------------------------------------===// // permutations(ofCount:) //===----------------------------------------------------------------------===// @@ -268,34 +286,41 @@ extension Collection { /// // Davide, Celeste /// /// This example prints _all_ the permutations (including an empty array) from - /// the an array of numbers: + /// 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] + /// 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. + /// The returned permutations are in ascending order by length, and then + /// lexicographically within each group of the same length. + /// + /// - Parameter kRange: A range of the number of elements to include in each + /// permutation. `kRange` can be any integer range expression, and is + /// clamped to the number of elements in this collection. Passing a range + /// covering sizes greater than the number of elements in this collection + /// results in an empty sequence. /// /// - 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. + /// is the number of elements in the base collection, since `Permutations` + /// accesses the `count` of the base collection. @inlinable public func permutations( ofCount kRange: R @@ -346,11 +371,12 @@ extension Collection { /// /// - Parameter k: The number of elements to include in each permutation. /// If `k` is `nil`, the resulting sequence represents permutations of this - /// entire collection. + /// entire collection. If `k` is greater than the number of elements in + /// this collection, the resulting sequence is empty. /// /// - 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. + /// 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 { precondition( @@ -359,3 +385,196 @@ extension Collection { return Permutations(self, k: k) } } + +//===----------------------------------------------------------------------===// +// uniquePermutations() +//===----------------------------------------------------------------------===// + +/// A sequence of the unique permutations of the elements of a sequence or +/// collection. +/// +/// To create a `UniquePermutations` instance, call one of the +/// `uniquePermutations` methods on your collection. +public struct UniquePermutations { + /// The base collection to iterate over for permutations. + public let base: Base + + @usableFromInline + internal var indexes: [Base.Index] + + @usableFromInline + internal let kRange: Range +} + +extension UniquePermutations where Base.Element: Hashable { + @inlinable + internal static func _indexes(_ base: Base) -> [Base.Index] { + let firstIndexesAndCountsByElement = Dictionary( + base.indices.lazy.map { (base[$0], ($0, 1)) }, + uniquingKeysWith: { indexAndCount, _ in (indexAndCount.0, indexAndCount.1 + 1) }) + + return firstIndexesAndCountsByElement + .values.sorted(by: { $0.0 < $1.0 }) + .flatMap { index, count in repeatElement(index, count: count) } + } + + @inlinable + internal init(_ elements: Base) { + self.indexes = Self._indexes(elements) + self.base = elements + self.kRange = self.indexes.count ..< (self.indexes.count + 1) + } + + @inlinable + internal init(_ base: Base, _ range: R) + where R.Bound == Int + { + self.indexes = Self._indexes(base) + self.base = base + + let upperBound = self.indexes.count + 1 + self.kRange = range.relative(to: 0 ..< .max) + .clamped(to: 0 ..< upperBound) + } +} + +extension UniquePermutations: Sequence { + /// The iterator for a `UniquePermutations` instance. + public struct Iterator: IteratorProtocol { + @usableFromInline + internal let base: Base + + @usableFromInline + internal var indexes: [Base.Index] + + @usableFromInline + internal var lengths: Range + + @usableFromInline + internal var initial = true + + @inlinable + internal init(_ elements: Base, indexes: [Base.Index], lengths: Range) { + self.base = elements + self.indexes = indexes + self.lengths = lengths + } + + @inlinable + public mutating func next() -> [Base.Element]? { + // In the end case, `lengths` is an empty range. + if lengths.isEmpty { + return nil + } + + // The first iteration must produce the original sorted array, before any + // permutations. We skip the permutation the first time so that we can + // always mutate the array _before_ returning a slice, which avoids + // copying when possible. + if initial { + initial = false + return indexes[.. Iterator { + Iterator(base, indexes: indexes, lengths: kRange) + } +} + +extension UniquePermutations: LazySequenceProtocol where Base: LazySequenceProtocol {} + +extension Collection where Element: Hashable { + /// Returns a sequence of the unique permutations of this sequence of the + /// specified length. + /// + /// Use this method to iterate over the unique permutations of a sequence + /// with repeating elements. This example prints every unique two-element + /// permutation of an array of numbers: + /// + /// let numbers = [1, 1, 2] + /// for perm in numbers.uniquePermutations(ofCount: 2) { + /// print(perm) + /// } + /// // [1, 1] + /// // [1, 2] + /// // [2, 1] + /// + /// By contrast, the `permutations(ofCount:)` method permutes a collection's + /// elements by position, and can include permutations with equal elements + /// in each permutation: + /// + /// for perm in numbers.permutations(ofCount: 2) + /// print(perm) + /// } + /// // [1, 1] + /// // [1, 1] + /// // [1, 2] + /// // [1, 2] + /// // [2, 1] + /// // [2, 1] + /// + /// The returned permutations are in lexicographically sorted order. + /// + /// - Parameter k: The number of elements to include in each permutation. + /// If `k` is `nil`, the resulting sequence represents permutations of this + /// entire collection. If `k` is greater than the number of elements in + /// this collection, the resulting sequence is empty. + /// + /// - Complexity: O(*n*), where *n* is the number of elements in this + /// collection. + public func uniquePermutations(ofCount k: Int? = nil) -> UniquePermutations { + if let k = k { + return UniquePermutations(self, k ..< (k + 1)) + } else { + return UniquePermutations(self) + } + } + + /// Returns a collection of the unique permutations of this sequence with + /// lengths in the specified range. + /// + /// Use this method to iterate over the unique permutations of a sequence + /// with repeating elements. This example prints every unique permutation + /// of an array of numbers with lengths through 2 elements: + /// + /// let numbers = [1, 1, 2] + /// for perm in numbers.uniquePermutations(ofCount: ...2) { + /// print(perm) + /// } + /// // [] + /// // [1] + /// // [2] + /// // [1, 1] + /// // [1, 2] + /// // [2, 1] + /// + /// The returned permutations are in ascending order by length, and then + /// lexicographically within each group of the same length. + /// + /// - Parameter kRange: A range of the number of elements to include in each + /// permutation. `kRange` can be any integer range expression, and is + /// clamped to the number of elements in this collection. Passing a range + /// covering sizes greater than the number of elements in this collection + /// results in an empty sequence. + /// + /// - Complexity: O(*n*), where *n* is the number of elements in this + /// collection. + public func uniquePermutations(ofCount kRange: R) -> UniquePermutations + where R.Bound == Int + { + UniquePermutations(self, kRange) + } +} diff --git a/Tests/SwiftAlgorithmsTests/UniquePermutationsTests.swift b/Tests/SwiftAlgorithmsTests/UniquePermutationsTests.swift new file mode 100644 index 00000000..39b785e3 --- /dev/null +++ b/Tests/SwiftAlgorithmsTests/UniquePermutationsTests.swift @@ -0,0 +1,122 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Algorithms open source project +// +// Copyright (c) 2020 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import XCTest +import Algorithms + +final class UniquePermutationsTests: XCTestCase { + static let numbers = [1, 1, 1, 2, 3] + + static let numbersPermutations: [[[Int]]] = [ + // k = 0 + [[]], + // 1 + [[1], [2], [3]], + // 2 + [[1, 1], [1, 2], [1, 3], [2, 1], [2, 3], [3, 1], [3, 2]], + // 3 + [[1, 1, 1], [1, 1, 2], [1, 1, 3], + [1, 2, 1], [1, 2, 3], [1, 3, 1], [1, 3, 2], + [2, 1, 1], [2, 1, 3], [2, 3, 1], + [3, 1, 1], [3, 1, 2], [3, 2, 1]], + // 4 + [[1, 1, 1, 2], [1, 1, 1, 3], + [1, 1, 2, 1], [1, 1, 2, 3], + [1, 1, 3, 1], [1, 1, 3, 2], + [1, 2, 1, 1], [1, 2, 1, 3], [1, 2, 3, 1], + [1, 3, 1, 1], [1, 3, 1, 2], [1, 3, 2, 1], + [2, 1, 1, 1], [2, 1, 1, 3], [2, 1, 3, 1], [2, 3, 1, 1], + [3, 1, 1, 1], [3, 1, 1, 2], [3, 1, 2, 1], [3, 2, 1, 1]], + // 5 + [[1, 1, 1, 2, 3], [1, 1, 1, 3, 2], + [1, 1, 2, 1, 3], [1, 1, 2, 3, 1], + [1, 1, 3, 1, 2], [1, 1, 3, 2, 1], + [1, 2, 1, 1, 3], [1, 2, 1, 3, 1], [1, 2, 3, 1, 1], + [1, 3, 1, 1, 2], [1, 3, 1, 2, 1], [1, 3, 2, 1, 1], + [2, 1, 1, 1, 3], [2, 1, 1, 3, 1], [2, 1, 3, 1, 1], [2, 3, 1, 1, 1], + [3, 1, 1, 1, 2], [3, 1, 1, 2, 1], [3, 1, 2, 1, 1], [3, 2, 1, 1, 1]] + ] +} + +extension UniquePermutationsTests { + func testEmpty() { + XCTAssertEqualSequences(([] as [Int]).uniquePermutations(), [[]]) + XCTAssertEqualSequences(([] as [Int]).uniquePermutations(ofCount: 0), [[]]) + XCTAssertEqualSequences(([] as [Int]).uniquePermutations(ofCount: 1), []) + XCTAssertEqualSequences( + ([] as [Int]).uniquePermutations(ofCount: 1...3), []) + } + + func testSingleCounts() { + for (k, expectation) in Self.numbersPermutations.enumerated() { + XCTAssertEqualSequences( + expectation, + Self.numbers.uniquePermutations(ofCount: k)) + } + + XCTAssertEqualSequences( + Self.numbersPermutations[5], + Self.numbers.uniquePermutations()) + } + + func testRanges() { + for lower in Self.numbersPermutations.indices { + // upper bounded + XCTAssertEqualSequences( + Self.numbersPermutations[...lower].joined(), + Self.numbers.uniquePermutations(ofCount: ...lower)) + + // lower bounded + XCTAssertEqualSequences( + Self.numbersPermutations[lower...].joined(), + Self.numbers.uniquePermutations(ofCount: lower...)) + + for upper in lower.. Bool { + lhs.value == rhs.value + } + + func hash(into hasher: inout Hasher) { + hasher.combine(value) + } + } + + func testFirstUnique() { + // When duplicate elements are encountered, all permutations use the first + // instance of the duplicated elements. + let numbers = Self.numbers.map(IntBox.init) + for k in 0...numbers.count { + for p in numbers.uniquePermutations(ofCount: k) { + XCTAssertTrue(p.filter { $0.value == 1 }.allSatisfy { $0 === numbers[0] }) + } + } + } + + func testLaziness() { + XCTAssertLazySequence("ABCD".lazy.uniquePermutations()) + } +}