Skip to content

Commit 5972a25

Browse files
authored
Merge pull request #3358 from apple/stdlib-nonrecursive-next-permutation
2 parents 51f364d + fd7f55d commit 5972a25

File tree

2 files changed

+78
-23
lines changed

2 files changed

+78
-23
lines changed

stdlib/private/StdlibUnittest/StdlibCoreExtras.swift

Lines changed: 77 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -134,36 +134,91 @@ extension TypeIdentifier
134134
}
135135
}
136136

137-
func _forAllPermutationsImpl(
138-
_ index: Int, _ size: Int,
139-
_ perm: inout [Int], _ visited: inout [Bool],
140-
_ body: ([Int]) -> Void
141-
) {
142-
if index == size {
143-
body(perm)
144-
return
137+
enum FormNextPermutationResult {
138+
case success
139+
case formedFirstPermutation
140+
}
141+
142+
extension MutableCollection
143+
where
144+
Self : BidirectionalCollection,
145+
Iterator.Element : Comparable
146+
{
147+
mutating func _reverseSubrange(_ subrange: Range<Index>) {
148+
if subrange.isEmpty { return }
149+
var f = subrange.lowerBound
150+
var l = index(before: subrange.upperBound)
151+
while f < l {
152+
swap(&self[f], &self[l])
153+
formIndex(after: &f)
154+
formIndex(before: &l)
155+
}
145156
}
146157

147-
for i in 0..<size {
148-
if visited[i] {
149-
continue
158+
mutating func formNextPermutation() -> FormNextPermutationResult {
159+
if isEmpty {
160+
// There are 0 elements, only one permutation is possible.
161+
return .formedFirstPermutation
162+
}
163+
164+
do {
165+
var i = startIndex
166+
formIndex(after: &i)
167+
if i == endIndex {
168+
// There is only element, only one permutation is possible.
169+
return .formedFirstPermutation
170+
}
171+
}
172+
173+
var i = endIndex
174+
formIndex(before: &i)
175+
var beforeI = i
176+
formIndex(before: &beforeI)
177+
var elementAtI = self[i]
178+
var elementAtBeforeI = self[beforeI]
179+
while true {
180+
if elementAtBeforeI < elementAtI {
181+
// Elements at `i..<endIndex` are in non-increasing order. To form the
182+
// next permutation in lexicographical order we need to replace
183+
// `self[i-1]` with the next larger element found in the tail, and
184+
// reverse the tail. For example:
185+
//
186+
// i-1 i endIndex
187+
// V V V
188+
// 6 2 8 7 4 1 [ ] // Input.
189+
// 6 (4) 8 7 (2) 1 [ ] // Exchanged self[i-1] with the
190+
// ^--------^ // next larger element
191+
// // from the tail.
192+
// 6 4 (1)(2)(7)(8)[ ] // Reversed the tail.
193+
// <-------->
194+
var j = endIndex
195+
repeat {
196+
formIndex(before: &j)
197+
} while !(elementAtBeforeI < self[j])
198+
swap(&self[beforeI], &self[j])
199+
_reverseSubrange(i..<endIndex)
200+
return .success
201+
}
202+
if beforeI == startIndex {
203+
// All elements are in non-increasing order. Reverse to form the first
204+
// pemutation, where all elements are sorted (in non-increasing order).
205+
reverse()
206+
return .formedFirstPermutation
207+
}
208+
i = beforeI
209+
formIndex(before: &beforeI)
210+
elementAtI = elementAtBeforeI
211+
elementAtBeforeI = self[beforeI]
150212
}
151-
visited[i] = true
152-
perm[index] = i
153-
_forAllPermutationsImpl(index + 1, size, &perm, &visited, body)
154-
visited[i] = false
155213
}
156214
}
157215

158216
/// Generate all permutations.
159217
public func forAllPermutations(_ size: Int, body: ([Int]) -> Void) {
160-
if size == 0 {
161-
return
162-
}
163-
164-
var permutation = [Int](repeating: 0, count: size)
165-
var visited = [Bool](repeating: false, count: size)
166-
_forAllPermutationsImpl(0, size, &permutation, &visited, body)
218+
var data = Array(0..<size)
219+
repeat {
220+
body(data)
221+
} while data.formNextPermutation() != .formedFirstPermutation
167222
}
168223

169224
/// Generate all permutations.

validation-test/StdlibUnittest/SequencesCollections.swift.gyb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ SequenceTypeAlgorithms.test("forAllPermutations") {
301301
forAllPermutations(0) {
302302
permutations.append($0)
303303
}
304-
expectEqualSequence([] as [[Int]], permutations) { $0 == $1 }
304+
expectEqualSequence([[]] as [[Int]], permutations) { $0 == $1 }
305305
}
306306

307307
do {

0 commit comments

Comments
 (0)