Skip to content

Commit 9e09d36

Browse files
committed
Convert to Hashable-based uniquing instead of Comparable
1 parent 7a2eda7 commit 9e09d36

File tree

2 files changed

+84
-112
lines changed

2 files changed

+84
-112
lines changed

Sources/Algorithms/Permutations.swift

Lines changed: 48 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ extension MutableCollection
2929
///
3030
/// - Complexity: O(*n*), where *n* is the length of the collection.
3131
@inlinable
32-
internal mutating func nextPermutation() -> Bool {
32+
internal mutating func nextPermutation2() -> Bool {
3333
// Ensure we have > 1 element in the collection.
3434
guard !isEmpty else { return false }
3535
var i = index(before: endIndex)
@@ -57,25 +57,25 @@ extension MutableCollection
5757
}
5858
}
5959
}
60-
}
6160

62-
extension MutableCollection where Self: BidirectionalCollection {
6361
@inlinable
64-
internal mutating func nextPermutation(upperBound: Index, by areInIncreasingOrder: (Element, Element) -> Bool) -> Bool {
62+
internal mutating func nextPermutation(upperBound: Index? = nil) -> Bool {
6563
// Ensure we have > 1 element in the collection.
6664
guard !isEmpty else { return false }
6765
var i = index(before: endIndex)
6866
if i == startIndex { return false }
6967

68+
let upperBound = upperBound ?? endIndex
69+
7070
while true {
7171
let ip1 = i
7272
formIndex(before: &i)
7373

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

8080
// At this point we have something like this:
8181
// 0, 1, 4, 3, 2
@@ -420,27 +420,35 @@ extension Collection {
420420
///
421421
/// To create a `UniquePermutations` instance, call one of the
422422
/// `uniquePermutations` methods on your collection.
423-
public struct UniquePermutations<Element> {
423+
public struct UniquePermutations<Base: Collection> where Base.Element: Hashable {
424+
@usableFromInline
425+
internal let elements: Base
426+
424427
@usableFromInline
425-
internal let elements: [Element]
428+
internal var indexes: [Base.Index]
426429

427430
@usableFromInline
428431
internal let kRange: Range<Int>
429432

430-
@usableFromInline
431-
internal let areInIncreasingOrder: (Element, Element) -> Bool
432-
433433
@inlinable
434-
internal init<S: Sequence>(_ elements: S, by areInIncreasingOrder: @escaping (Element, Element) -> Bool) where S.Element == Element {
435-
self.init(elements, 0..<Int.max, by: areInIncreasingOrder)
434+
internal init(_ elements: Base) {
435+
self.init(elements, 0..<Int.max)
436436
}
437437

438438
@inlinable
439-
internal init<S: Sequence, R: RangeExpression>(_ elements: S, _ range: R, by areInIncreasingOrder: @escaping (Element, Element) -> Bool)
440-
where S.Element == Element, R.Bound == Int
439+
internal init<R: RangeExpression>(_ elements: Base, _ range: R)
440+
where R.Bound == Int
441441
{
442-
self.elements = elements.sorted(by: areInIncreasingOrder)
443-
self.areInIncreasingOrder = areInIncreasingOrder
442+
let firstIndexesAndCountsByElement = Dictionary(
443+
elements.indices.lazy.map { (elements[$0], ($0, 1)) },
444+
uniquingKeysWith: { indexAndCount, _ in (indexAndCount.0, indexAndCount.1 + 1) })
445+
446+
self.indexes = Array(firstIndexesAndCountsByElement
447+
.values.sorted(by: { $0.0 < $1.0 })
448+
.map { index, count in repeatElement(index, count: count) }
449+
.joined())
450+
451+
self.elements = elements
444452

445453
let upperBound = self.elements.count + 1
446454
self.kRange = range.relative(to: 0 ..< .max)
@@ -449,29 +457,31 @@ public struct UniquePermutations<Element> {
449457
}
450458

451459
extension UniquePermutations: Sequence {
460+
public typealias Element = [Base.Element]
461+
452462
/// The iterator for a `UniquePermutations` instance.
453463
public struct Iterator: IteratorProtocol {
454464
@usableFromInline
455-
var elements: [Element]
456-
457-
@usableFromInline
458-
var initial = true
465+
internal let elements: Base
459466

460467
@usableFromInline
461-
var lengths: Range<Int>
468+
internal var indexes: [Base.Index]
462469

463470
@usableFromInline
464-
internal let areInIncreasingOrder: (Element, Element) -> Bool
465-
471+
internal var lengths: Range<Int>
472+
473+
@usableFromInline
474+
internal var initial = true
475+
466476
@inlinable
467-
init(_ elements: [Element], lengths: Range<Int>, by areInIncreasingOrder: @escaping (Element, Element) -> Bool) {
477+
internal init(_ elements: Base, indexes: [Base.Index], lengths: Range<Int>) {
468478
self.elements = elements
479+
self.indexes = indexes
469480
self.lengths = lengths
470-
self.areInIncreasingOrder = areInIncreasingOrder
471481
}
472482

473483
@inlinable
474-
public mutating func next() -> ArraySlice<Element>? {
484+
public mutating func next() -> [Base.Element]? {
475485
// In the end case, `lengths` is an empty range.
476486
if lengths.isEmpty {
477487
return nil
@@ -483,28 +493,28 @@ extension UniquePermutations: Sequence {
483493
// copying when possible.
484494
if initial {
485495
initial = false
486-
return elements[..<lengths.lowerBound]
496+
return indexes[..<lengths.lowerBound].map { elements[$0] }
487497
}
488498

489-
if !elements.nextPermutation(upperBound: lengths.lowerBound, by: areInIncreasingOrder) {
499+
if !indexes.nextPermutation(upperBound: lengths.lowerBound) {
490500
lengths = (lengths.lowerBound + 1)..<lengths.upperBound
491501

492502
if lengths.isEmpty {
493503
return nil
494504
}
495505
}
496506

497-
return elements[..<lengths.lowerBound]
507+
return indexes[..<lengths.lowerBound].map { elements[$0] }
498508
}
499509
}
500510

501511
@inlinable
502512
public func makeIterator() -> Iterator {
503-
Iterator(elements, lengths: kRange, by: areInIncreasingOrder)
513+
Iterator(elements, indexes: indexes, lengths: kRange)
504514
}
505515
}
506516

507-
extension Sequence where Element: Comparable {
517+
extension Collection where Element: Hashable {
508518
/// Returns a sequence of the unique permutations of this sequence.
509519
///
510520
/// Use this method to iterate over the unique permutations of a sequence
@@ -534,8 +544,8 @@ extension Sequence where Element: Comparable {
534544
/// // [2, 2, 1]
535545
///
536546
/// The returned permutations are in lexicographically sorted order.
537-
public func uniquePermutations() -> UniquePermutations<Element> {
538-
UniquePermutations(self, by: <)
547+
public func uniquePermutations() -> UniquePermutations<Self> {
548+
UniquePermutations(self)
539549
}
540550

541551
/// Returns a sequence of the unique permutations of this sequence of the
@@ -568,8 +578,8 @@ extension Sequence where Element: Comparable {
568578
/// // [2, 1]
569579
///
570580
/// The returned permutations are in lexicographically sorted order.
571-
public func uniquePermutations(ofCount k: Int) -> UniquePermutations<Element> {
572-
UniquePermutations(self, k ..< (k + 1), by: <)
581+
public func uniquePermutations(ofCount k: Int) -> UniquePermutations<Self> {
582+
UniquePermutations(self, k ..< (k + 1))
573583
}
574584

575585
/// Returns a collection of the unique permutations of this sequence with
@@ -592,39 +602,9 @@ extension Sequence where Element: Comparable {
592602
///
593603
/// The returned permutations are in ascending order by length, and then
594604
/// lexicographically within each group of the same length.
595-
public func uniquePermutations<R: RangeExpression>(ofCount kRange: R) -> UniquePermutations<Element>
596-
where R.Bound == Int
597-
{
598-
UniquePermutations(self, kRange, by: <)
599-
}
600-
}
601-
602-
extension Sequence {
603-
/// Returns a sequence of the unique permutations of this sequence.
604-
public func uniquePermutations(
605-
by areInIncreasingOrder: @escaping (Element, Element) -> Bool
606-
) -> UniquePermutations<Element> {
607-
UniquePermutations(self, by: areInIncreasingOrder)
608-
}
609-
610-
/// Returns a sequence of the unique permutations of this sequence of the
611-
/// specified length.
612-
///
613-
public func uniquePermutations(
614-
ofCount k: Int,
615-
by areInIncreasingOrder: @escaping (Element, Element) -> Bool
616-
) -> UniquePermutations<Element> {
617-
UniquePermutations(self, k ..< (k + 1), by: areInIncreasingOrder)
618-
}
619-
620-
/// Returns a collection of the unique permutations of this sequence with
621-
/// lengths in the specified range.
622-
public func uniquePermutations<R: RangeExpression>(
623-
ofCount kRange: R,
624-
by areInIncreasingOrder: @escaping (Element, Element) -> Bool
625-
) -> UniquePermutations<Element>
605+
public func uniquePermutations<R: RangeExpression>(ofCount kRange: R) -> UniquePermutations<Self>
626606
where R.Bound == Int
627607
{
628-
UniquePermutations(self, kRange, by: areInIncreasingOrder)
608+
UniquePermutations(self, kRange)
629609
}
630610
}

Tests/SwiftAlgorithmsTests/UniquePermutationsTests.swift

Lines changed: 36 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,10 @@
1212
import XCTest
1313
import Algorithms
1414

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-
}
22-
2315
final class UniquePermutationsTests: XCTestCase {
2416
static let numbers = [1, 1, 1, 2, 3]
2517

26-
static let numbersPermutations: [[ArraySlice<Int>]] = [
18+
static let numbersPermutations: [[[Int]]] = [
2719
// 0
2820
[[]],
2921
// 1
@@ -96,39 +88,39 @@ extension UniquePermutationsTests {
9688
}
9789
}
9890

99-
func testEmptyWithPredicate() {
100-
XCTAssertEqualSequences(([] as [Int?]).uniquePermutations(by: <), [[]])
101-
XCTAssertEqualSequences(([] as [Int?]).uniquePermutations(ofCount: 0, by: <), [[]])
102-
XCTAssertEqualSequences(([] as [Int?]).uniquePermutations(ofCount: 1, by: <), [])
103-
XCTAssertEqualSequences(([] as [Int?]).uniquePermutations(ofCount: 1...3, by: <), [])
104-
}
105-
106-
func testSingleCountsWithPredicate() {
107-
for (k, expectation) in Self.numbersAndNilsPermutations.enumerated() {
108-
XCTAssertEqualSequences(expectation, Self.numbersAndNils.uniquePermutations(ofCount: k, by: <))
109-
}
110-
}
111-
112-
func testRangesWithPredicate() {
113-
let numbersAndNils = Self.numbersAndNils
114-
let numbersAndNilsPermutations = Self.numbersAndNilsPermutations
115-
116-
for lower in numbersAndNilsPermutations.indices {
117-
// upper bounded
118-
XCTAssertEqualSequences(
119-
numbersAndNilsPermutations[...lower].joined(),
120-
numbersAndNils.uniquePermutations(ofCount: ...lower, by: <))
121-
122-
// lower bounded
123-
XCTAssertEqualSequences(
124-
numbersAndNilsPermutations[lower...].joined(),
125-
numbersAndNils.uniquePermutations(ofCount: lower..., by: <))
126-
127-
for upper in lower..<numbersAndNilsPermutations.count {
128-
XCTAssertEqualSequences(
129-
numbersAndNilsPermutations[lower..<upper].joined(),
130-
numbersAndNils.uniquePermutations(ofCount: lower..<upper, by: <))
131-
}
132-
}
133-
}
91+
// func testEmptyWithPredicate() {
92+
// XCTAssertEqualSequences(([] as [Int?]).uniquePermutations(by: <), [[]])
93+
// XCTAssertEqualSequences(([] as [Int?]).uniquePermutations(ofCount: 0, by: <), [[]])
94+
// XCTAssertEqualSequences(([] as [Int?]).uniquePermutations(ofCount: 1, by: <), [])
95+
// XCTAssertEqualSequences(([] as [Int?]).uniquePermutations(ofCount: 1...3, by: <), [])
96+
// }
97+
//
98+
// func testSingleCountsWithPredicate() {
99+
// for (k, expectation) in Self.numbersAndNilsPermutations.enumerated() {
100+
// XCTAssertEqualSequences(expectation, Self.numbersAndNils.uniquePermutations(ofCount: k, by: <))
101+
// }
102+
// }
103+
//
104+
// func testRangesWithPredicate() {
105+
// let numbersAndNils = Self.numbersAndNils
106+
// let numbersAndNilsPermutations = Self.numbersAndNilsPermutations
107+
//
108+
// for lower in numbersAndNilsPermutations.indices {
109+
// // upper bounded
110+
// XCTAssertEqualSequences(
111+
// numbersAndNilsPermutations[...lower].joined(),
112+
// numbersAndNils.uniquePermutations(ofCount: ...lower, by: <))
113+
//
114+
// // lower bounded
115+
// XCTAssertEqualSequences(
116+
// numbersAndNilsPermutations[lower...].joined(),
117+
// numbersAndNils.uniquePermutations(ofCount: lower..., by: <))
118+
//
119+
// for upper in lower..<numbersAndNilsPermutations.count {
120+
// XCTAssertEqualSequences(
121+
// numbersAndNilsPermutations[lower..<upper].joined(),
122+
// numbersAndNils.uniquePermutations(ofCount: lower..<upper, by: <))
123+
// }
124+
// }
125+
// }
134126
}

0 commit comments

Comments
 (0)