ORIGINAL WORDING
(SKIP TO THE UPDATE BELOW)
- Let's assume that the
n
elements are the integers 1..n
.
- Represent every
k
-combination in increasing order (this representation gets rid of permutations inside the k
-combination.)
- Now consider the lexicographic order between k-combinations (of
n
elements). In other words, {i_1..i_k} < {j_1..j_k}
if there exists an index t
such that
i_s = j_s
for all s < t
and
i_t < j_t
.
- If
{i_1..i_k}
is a k
-combination, define next{i_1..i_k}
to be the next element w.r.t. the lexicographic order.
Here is how to compute next{i_1..i_k}
:
- Find the largest index
r
such that i_r + 1 < i_{r+1}
- If no index satisfies this condition and
i_k < n
, set r := k
- If none of the above conditions can be satisfied, there is no next (and the
k
-combination equals {n-k+1, n-k+2,... ,n}
)
- If
r
satisfies the first condition, set next
to be {i_1, ..., i_{r-1}, i_r + 1, i_{r+1}, ..., i_k}
- If
r = k
(second condition), set next := {i_1, ..., i_{k-1}, i_k + 1}.
UPDATE (Many thanks to @rici for improving the solution)
- Let's assume that the
n
elements are the integers 1..n
.
- Represent every
k
-combination in increasing order (this representation gets rid of permutations inside the k
-combination.)
- Now consider the lexicographic order between k-combinations (of
n
elements). In other words, {i_1..i_k} < {j_1..j_k}
if there exists an index t
such that
i_s = j_s
for all s < t
and
i_t < j_t
.
- If
{i_1..i_k}
is a k
-combination, define next{i_1..i_k}
to be the next element w.r.t. the lexicographic order.
- Note that with this order the smallest
k
-combination is {1..k}
and the largest {n-k+1, n-k+2,... ,n}
.
Here is how to compute next{i_1..i_k}
- Find the largest index
r
such that i_r
can be incremented by 1
.
- Increment the value at index
r
and reset the following elements with consecutive values starting at i_r + 2
.
- Repeat until no position can be incremented.
More precisely:
- If
i_k < n
, increment i_k
by 1
(i.e., replace i_k
with i_k + 1
)
- If
i_k = n
, find the largest index r
such that i_r + 1 < i_{r+1}
. Then increment i_r
by 1
and reset the following positions to {i_r + 2, i_r + 3, ..., i_r + k + 1 - r}
- Repeat until you reach
{n-k+1, n-k+2,... ,n}
Note the recursive character of the algorithm: every time it increments the least significant position the tail is reset to the lexicographically smallest sequence that starts with the value just incremented.
Smalltalk code
SequenceableCollection >> #nextChoiceFrom: n
| next k r ar |
k := self size.
(self at: 1) = (n - k + 1) ifTrue: [^nil].
next := self shallowCopy.
r := (self at: k) = n
ifTrue: [(1 to: k-1) findLast: [:i | (self at: i) + 1 < (self at: i+1)]]
ifFalse: [k].
ar := self at: r.
r to: k do: [:i |
ar := ar + 1.
next at: i put: ar].
^next