apple / swift-algorithms

Commonly used sequence and collection algorithms for Swift
Apache License 2.0
5.97k stars 443 forks source link

Add uniquePermutations as wrapper for nextPermutation #91

Closed natecook1000 closed 3 years ago

natecook1000 commented 3 years ago

Description

Adds methods for generating the unique permutations of a sequence, as distinct from the position-based permutations generated by permutations().

Detailed Design

Includes new uniquePermutations(ofCount:) methods (for both single counts and range expressions) and the wrapping UniquePermutations type. The method requires that a collection's elements be hashable so that uniqueness can be determined in a pre-processing pass:

extension Collection where Element: Hashable {
  /// Returns a sequence of the unique permutations of this collection.
  ///
  /// Use this method to iterate over the unique permutations of a sequence
  /// with repeating elements. This example prints every permutation of an
  /// array of numbers:
  ///
  ///     let numbers = [1, 2, 2]
  ///     for perm in numbers.uniquePermutations() {
  ///         print(perm)
  ///     }
  ///     // [1, 2, 2]
  ///     // [2, 1, 2]
  ///     // [2, 2, 1]
  public func uniquePermutations(ofCount k: Int) -> UniquePermutations<Element>

  public func uniquePermutations<R>(ofCount k: Int) -> UniquePermutations<Element>
    where R: RangeExpression, R.Bound == Int
}

Documentation Plan

I've added API documentation on the new methods and type, and have updated Permutations.md to include information about unique permutations.

Test Plan

I've created tests in the UniquePermutationsTests.swift file.

Source Impact

None, additive only.

Checklist

kylemacomber commented 3 years ago

I think we actually want uniquePermutations(ofCount:).

I just wanted this for #107 where I wrote:

var tests: [[Int?]] = [
  [],
  [0],
  [nil],
  [0, nil],
  [nil, 0],
  [0, nil, 1, nil, 2, nil],
  [0, 1, 2, nil, nil, nil],
  [nil, nil, nil, 0, 1, 2],
]

I would have preferred to write:

let tests = [nil, nil, nil, 0, 1, 2].uniquePermutations(ofCount: 0)

... which also would have given me more exhaustive test coverage.

natecook1000 commented 3 years ago

Totally agreed, though that points to the need for a by areInIncreasingOrder version as well, since optionals are no longer Comparable. Luckily, the closure's easy to write! 😜

let tests = [nil, nil, nil, 0, 1, 2]
    .uniquePermutations(ofCount: 0...) { lhs, rhs in
        rhs.map { rhs in lhs.map { lhs in lhs < rhs } ?? true } ?? false
    }
natecook1000 commented 3 years ago

@swift-ci Please test

natecook1000 commented 3 years ago

@swift-ci Please test

natecook1000 commented 3 years ago

@swift-ci Please test

natecook1000 commented 3 years ago

@swift-ci Please test