Skip to content

Commit d895e67

Browse files
committed
Move to a predicate-based uniquePermutations
1 parent 5dc3f84 commit d895e67

File tree

2 files changed

+94
-21
lines changed

2 files changed

+94
-21
lines changed

Sources/Algorithms/Permutations.swift

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,11 @@ extension MutableCollection
5757
}
5858
}
5959
}
60+
}
6061

62+
extension MutableCollection where Self: BidirectionalCollection {
6163
@inlinable
62-
internal mutating func nextPermutation(upperBound: Index) -> Bool {
64+
internal mutating func nextPermutation(upperBound: Index, by areInIncreasingOrder: (Element, Element) -> Bool) -> Bool {
6365
// Ensure we have > 1 element in the collection.
6466
guard !isEmpty else { return false }
6567
var i = index(before: endIndex)
@@ -70,10 +72,10 @@ extension MutableCollection
7072
formIndex(before: &i)
7173

7274
// Find the last ascending pair (ie. ..., a, b, ... where a < b)
73-
if self[i] < self[ip1] {
75+
if areInIncreasingOrder(self[i], self[ip1]) {
7476
// Find the last element greater than self[i]
7577
// This is _always_ at most `ip1` due to if statement above
76-
let j = lastIndex(where: { $0 > self[i] })!
78+
let j = lastIndex(where: { areInIncreasingOrder(self[i], $0) })!
7779

7880
// At this point we have something like this:
7981
// 0, 1, 4, 3, 2
@@ -410,23 +412,27 @@ extension Collection {
410412
// uniquePermutations()
411413
//===----------------------------------------------------------------------===//
412414

413-
public struct UniquePermutations<Element: Comparable> {
415+
public struct UniquePermutations<Element> {
414416
@usableFromInline
415417
internal let elements: [Element]
416418

417419
@usableFromInline
418420
internal let kRange: Range<Int>
419421

422+
@usableFromInline
423+
internal let areInIncreasingOrder: (Element, Element) -> Bool
424+
420425
@inlinable
421-
internal init<S: Sequence>(_ elements: S) where S.Element == Element {
422-
self.init(elements, 0..<Int.max)
426+
internal init<S: Sequence>(_ elements: S, by areInIncreasingOrder: @escaping (Element, Element) -> Bool) where S.Element == Element {
427+
self.init(elements, 0..<Int.max, by: areInIncreasingOrder)
423428
}
424429

425430
@inlinable
426-
internal init<S: Sequence, R: RangeExpression>(_ elements: S, _ range: R)
431+
internal init<S: Sequence, R: RangeExpression>(_ elements: S, _ range: R, by areInIncreasingOrder: @escaping (Element, Element) -> Bool)
427432
where S.Element == Element, R.Bound == Int
428433
{
429-
self.elements = elements.sorted()
434+
self.elements = elements.sorted(by: areInIncreasingOrder)
435+
self.areInIncreasingOrder = areInIncreasingOrder
430436

431437
let upperBound = self.elements.count + 1
432438
self.kRange = range.relative(to: 0 ..< .max)
@@ -438,22 +444,21 @@ extension UniquePermutations: Sequence {
438444
public struct Iterator: IteratorProtocol {
439445
@usableFromInline
440446
var elements: [Element]
441-
442-
@usableFromInline
443-
enum State {
444-
case start, middle, end
445-
}
446-
447+
447448
@usableFromInline
448449
var initial = true
449450

450451
@usableFromInline
451452
var lengths: Range<Int>
452453

454+
@usableFromInline
455+
internal let areInIncreasingOrder: (Element, Element) -> Bool
456+
453457
@inlinable
454-
init(_ elements: [Element], lengths: Range<Int>) {
458+
init(_ elements: [Element], lengths: Range<Int>, by areInIncreasingOrder: @escaping (Element, Element) -> Bool) {
455459
self.elements = elements
456460
self.lengths = lengths
461+
self.areInIncreasingOrder = areInIncreasingOrder
457462
}
458463

459464
@inlinable
@@ -472,7 +477,7 @@ extension UniquePermutations: Sequence {
472477
return elements[..<lengths.lowerBound]
473478
}
474479

475-
if !elements.nextPermutation(upperBound: lengths.lowerBound) {
480+
if !elements.nextPermutation(upperBound: lengths.lowerBound, by: areInIncreasingOrder) {
476481
lengths = (lengths.lowerBound + 1)..<lengths.upperBound
477482

478483
if lengths.isEmpty {
@@ -486,7 +491,7 @@ extension UniquePermutations: Sequence {
486491

487492
@inlinable
488493
public func makeIterator() -> Iterator {
489-
Iterator(elements, lengths: kRange)
494+
Iterator(elements, lengths: kRange, by: areInIncreasingOrder)
490495
}
491496
}
492497

@@ -521,16 +526,32 @@ extension Sequence where Element: Comparable {
521526
///
522527
/// The returned permutations are in lexicographically sorted order.
523528
public func uniquePermutations() -> UniquePermutations<Element> {
524-
UniquePermutations(self)
529+
UniquePermutations(self, by: <)
525530
}
526531

527532
public func uniquePermutations(ofCount k: Int) -> UniquePermutations<Element> {
528-
UniquePermutations(self, k ..< (k + 1))
533+
UniquePermutations(self, k ..< (k + 1), by: <)
529534
}
530535

531536
public func uniquePermutations<R: RangeExpression>(ofCount kRange: R) -> UniquePermutations<Element>
532537
where R.Bound == Int
533538
{
534-
UniquePermutations(self, kRange)
539+
UniquePermutations(self, kRange, by: <)
540+
}
541+
}
542+
543+
extension Sequence {
544+
public func uniquePermutations(by areInIncreasingOrder: @escaping (Element, Element) -> Bool) -> UniquePermutations<Element> {
545+
UniquePermutations(self, by: areInIncreasingOrder)
546+
}
547+
548+
public func uniquePermutations(ofCount k: Int, by areInIncreasingOrder: @escaping (Element, Element) -> Bool) -> UniquePermutations<Element> {
549+
UniquePermutations(self, k ..< (k + 1), by: areInIncreasingOrder)
550+
}
551+
552+
public func uniquePermutations<R: RangeExpression>(ofCount kRange: R, by areInIncreasingOrder: @escaping (Element, Element) -> Bool) -> UniquePermutations<Element>
553+
where R.Bound == Int
554+
{
555+
UniquePermutations(self, kRange, by: areInIncreasingOrder)
535556
}
536557
}

Tests/SwiftAlgorithmsTests/UniquePermutationsTests.swift

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,15 @@
1010
//===----------------------------------------------------------------------===//
1111

1212
import XCTest
13-
@testable import Algorithms
13+
import Algorithms
14+
15+
fileprivate func < <T: Comparable>(lhs: T?, rhs: T?) -> Bool {
16+
switch (lhs, rhs) {
17+
case (let lhs?, let rhs?): return lhs < rhs
18+
case (nil, _?): return true
19+
case (_, nil): return false
20+
}
21+
}
1422

1523
final class UniquePermutationsTests: XCTestCase {
1624
static let numbers = [1, 1, 1, 2, 3]
@@ -44,6 +52,14 @@ final class UniquePermutationsTests: XCTestCase {
4452
[2, 1, 1, 1, 3], [2, 1, 1, 3, 1], [2, 1, 3, 1, 1], [2, 3, 1, 1, 1],
4553
[3, 1, 1, 1, 2], [3, 1, 1, 2, 1], [3, 1, 2, 1, 1], [3, 2, 1, 1, 1]]
4654
]
55+
56+
static var numbersAndNils: [Int?] {
57+
numbers.map { $0 == 1 ? nil : $0 }
58+
}
59+
60+
static var numbersAndNilsPermutations: [[ArraySlice<Int?>]] {
61+
numbersPermutations.map { $0.map { ArraySlice($0.map { $0 == 1 ? nil : $0 }) }}
62+
}
4763

4864
func testEmpty() {
4965
XCTAssertEqualSequences(([] as [Int]).uniquePermutations(), [[]])
@@ -77,4 +93,40 @@ final class UniquePermutationsTests: XCTestCase {
7793
}
7894
}
7995
}
96+
97+
func testEmptyWithPredicate() {
98+
XCTAssertEqualSequences(([] as [Int?]).uniquePermutations(by: <), [[]])
99+
XCTAssertEqualSequences(([] as [Int?]).uniquePermutations(ofCount: 0, by: <), [[]])
100+
XCTAssertEqualSequences(([] as [Int?]).uniquePermutations(ofCount: 1, by: <), [])
101+
XCTAssertEqualSequences(([] as [Int?]).uniquePermutations(ofCount: 1...3, by: <), [])
102+
}
103+
104+
func testSingleCountsWithPredicate() {
105+
for (k, expectation) in Self.numbersAndNilsPermutations.enumerated() {
106+
XCTAssertEqualSequences(expectation, Self.numbersAndNils.uniquePermutations(ofCount: k, by: <))
107+
}
108+
}
109+
110+
func testRangesWithPredicate() {
111+
let numbersAndNils = Self.numbersAndNils
112+
let numbersAndNilsPermutations = Self.numbersAndNilsPermutations
113+
114+
for lower in numbersAndNilsPermutations.indices {
115+
// upper bounded
116+
XCTAssertEqualSequences(
117+
numbersAndNilsPermutations[...lower].joined(),
118+
numbersAndNils.uniquePermutations(ofCount: ...lower, by: <))
119+
120+
// lower bounded
121+
XCTAssertEqualSequences(
122+
numbersAndNilsPermutations[lower...].joined(),
123+
numbersAndNils.uniquePermutations(ofCount: lower..., by: <))
124+
125+
for upper in lower..<numbersAndNilsPermutations.count {
126+
XCTAssertEqualSequences(
127+
numbersAndNilsPermutations[lower..<upper].joined(),
128+
numbersAndNils.uniquePermutations(ofCount: lower..<upper, by: <))
129+
}
130+
}
131+
}
80132
}

0 commit comments

Comments
 (0)