Instead of copying one range into another, it is sometimes useful to swap two ranges of the same size: to exchange the values of objects in corresponding positions. Swapping algorithms are very similar to copying algorithms, except that assignment is replaced by a procedure that exchanges the values of objects pointed to by two mutable iterators:
template<typename I0, typename I1>
requires(Mutable(I0) && Mutable(I1) && ValueType(I0) == ValueType(I1)) void exchange_values(I0 x, I1 y)
{
// Precondition: deref(x) and deref(y) are defined ValueType(I0) t = source(x); sink(x) = source(y); sink(y) = t; } Exercise 9.7. Lemma 9.2.
We would like the implementation of exchange_values to avoid actually constructing or destroying any objects but simply to exchange the values of two objects, so that its cost does not increase with the amount of resources owned by the objects. We accomplish this goal in Chapter 12 with a notion of underlying type.
As with copying, we construct the swapping algorithms from machines that take two iterators by reference and are responsible for both exchanging and updating the iterators. One machine exchanges two objects and then increments both iterators:
template<typename I0, typename I1>
requires(Mutable(I0) && ForwardIterator(I0) && Mutable(I1) && ForwardIterator(I1) && ValueType(I0) == ValueType(I1)) void swap_step(I0& f0, I1& f1)
{
// Precondition: deref(f0) and deref(f
1) are defined exchange values(f0, f1);
f0 = successor(f0); f1 = successor(f1); }
This leads to the first algorithm, which is analogous to copy:
template<typename I0, typename I1>
requires(Mutable(I0) && ForwardIterator(I0) && What is the postcondition of exchange_values?
The effects of exchange_values(i, j) and exchange_values(j, i) are equivalent.
Mutable(I1) && ForwardIterator(I1) && ValueType(I0) == ValueType(I1)) I1 swap_ranges(I0 f0, I0 l0, I1 f1) { // Precondition: mutable_bounded_range(f0, l0) // Precondition: mutable_counted_range(f1, l0 – f 0) while (f0 != l0) swap_step(f0, f1); return f1; }
The second algorithm is analogous to copy_bounded:
template<typename I0, typename I1>
requires(Mutable(I0) && ForwardIterator(I0) && Mutable(I1) && ForwardIterator(I1) && ValueType(I0) == ValueType(I1))
pair<I0, I1> swap_ranges_bounded(I0 f0, I0 l0, I1 f1, I1 l1) {
// Precondition: mutable_bounded_range(f0, l0) // Precondition: mutable_bounded_range(f1, l1) while (f0 != l0 && f1 != l1) swap_step(f0, f1); return pair<I0, I1>(f0, f1);
}
The third algorithm is analogous to copy_n:
template<typename I0, typename I1, typename N> requires(Mutable(I0) && ForwardIterator(I0) && Mutable(I1) && ForwardIterator(I1) && ValueType(I0) == ValueType(I1) && Integer(N))
pair<I0, I1> swap_ranges n(I0 f0, I1 f1, N n) {
// Precondition: mutable_counted_range(f0, n) // Precondition: mutable_counted_range(f1, n) while (count_down(n)) swap_step(f0, f1); return pair<I0, I1>(f0, f1);
}
When the ranges passed to the range-swapping algorithms do not overlap, it is apparent that their effect is to exchange the values of objects in corresponding positions. In the next chapter, we derive the postcondition for the overlapping case.
Reverse copying results in a copy in which positions are reversed from the original; reverse swapping is analogous. It requires a second machine, which moves backward in the first range and forward in the second range:
template<typename I0, typename I1>
requires(Mutable(I0) && BidirectionalIterator(I0) && Mutable(I1) && ForwardIterator(I1) &&
ValueType(I0) == ValueType(I1)) void reverse_swap_step(I0& l0, I1& f1) {
// Precondition: deref(predecessor(l0)) and deref(f1) are defined l0 = predecessor(l0);
exchange_values(l0, f1); f1 = successor(f1); }
Because of the symmetry of exchange_values, reverse_swap_ranges can be used whenever at least one iterator type is bidirectional; no backward versions are needed:
template<typename I0, typename I1>
requires(Mutable(I0) && BidirectionalIterator(I0) && Mutable(I1) && ForwardIterator(I1) &&
ValueType(I0) == ValueType(I1)) I1 reverse_swap_ranges(I0 f0, I0 l0, I1 f1) { // Precondition: mutable_bounded_range(f0, l0) // Precondition: mutable_counted_range(f1, l0 – f0) while (f0 != l0) reverse_swap_step(l0, f1); return f1; }
template<typename I0, typename I1>
requires(Mutable(I0) && BidirectionalIterator(I0) && Mutable(I1) && ForwardIterator(I1) &&
ValueType(I0) == ValueType(I1)) pair<I0, I1>reverse_swap_ranges_bounded(I0 f0, I0 l0, I1 f1, I1 l1) { // Precondition: mutable_bounded_range(f0, l0) // Precondition: mutable_bounded_range(f1, l1) while (f0 != l0 && f1 != l1) reverse_swap_step(l0, f1); return pair<I0, I1>(l0, f1); }
template<typename I0, typename I1, typename N>
requires(Mutable(I0) && BidirectionalIterator(I0) && Mutable(I1) && ForwardIterator(I1) &&
ValueType(I0) == ValueType(I1) && Integer(N))
pair<I0, I1> reverse_swap_ranges_n(I0 l0, I1 f1, N n) {
// Precondition: mutable_counted_range(l0 – n, n) // Precondition: mutable_counted_range(f1, n) while (count_down(n)) reverse_swap_step(l0, f1); return pair<I0, I1>(l0, f1);
}
Algorithms C++ Software Engineering Programming Alexander Stepanov Paul McJones Addison-Wesley Professional Elements of Programming
9.5. Conclusions
Extending an iterator type with sink leads to writability and mutability. Although the axiom for sink is simple, the issues of aliasing and of concurrent updates—which this book does not treat—make imperative programming complicated. In particular, defining preconditions that deal with aliasing through different iterator types requires great care. Copying algorithms are simple, powerful, and widely used. Composing these algorithms from simple machines helps to organize them into a family by identifying commonalities and suggesting additional variations. Using value exchange instead of value assignment leads to an analogous but slightly smaller family of useful range-swapping algorithms.
Algorithms C++ Software Engineering Programming Alexander Stepanov Paul McJones Addison-Wesley Professional Elements of Programming
Chapter 10. Rearrangements
This chapter introduces the concept of permutation and a taxonomy for a class of algorithms, called rearrangements, that permute the elements of a range to satisfy a given postcondition. We provide iterative algorithms of reverse for bidirectional and random-access iterators, and a divide-and- conquer algorithm for reverse on forward iterators. We show how to transform divide-and-conquer algorithms to make them run faster when extra memory is available. We describe three rotation algorithms corresponding to different iterator concepts, where rotation is the interchange of two adjacent ranges of not necessarily equal size. We conclude with a discussion of how to package algorithms for compile-time selection based on their requirements.
Algorithms C++ Software Engineering Programming Alexander Stepanov Paul McJones Addison-Wesley Professional Elements of Programming
10.1. Permutations
A transformation f is an into transformation if, for all x in its definition space, there exists a y in its definition space such that y = f(x). A transformation f is an onto transformation if, for all y in its definition space, there exists an x in its definition space such that y = f(x). A transformation f is a one- to-one transformation if, for all x, x' in its definition space, f(x) = f(x') x = x'.
Lemma 10.1.
Exercise 10.1.
A fixed point of a transformation is an element x such that f(x) = x. An identity transformation is one that has every element of its definition space as a fixed point. We denote the identity transformation on a set S as identityS.
A permutation is an onto transformation on a finite definition space. An example of a permutation on [0, 6):
A transformation on a finite definition space is an onto transformation if and only if it is both an into and one-to-one transformation.
Find a transformation of the natural numbers that is both an into and onto transformation but not a one-to-one transformation, and one that is both an into and one-to-one
transformation but not an onto transformation.
p (0) = 5 p (1) = 2 p (2) = 4 p (3) = 3 p (4) = 1 p (5) = 0
If p and q are two permutations on a set S, the composition q p takes x ϵ S to q(p(x)).
Lemma 10.2.
Lemma 10.3.
Lemma 10.4.
The permutations on a set form a group under composition.
Lemma 10.5.
For example, the multiplication group modulo 5 has the following multiplication table:
Every row and column of the multiplication table is a permutation. Since not every one of the 4! = 24 permutations of four elements appears in it, the multiplication group modulo 5 is therefore a proper subgroup of the permutation group of four elements.
A cycle is a circular orbit within a permutation. A trivial cycle is one with a cycle size of 1; the element in a trivial cycle is a fixed point. A permutation containing a single nontrivial cycle is called a cyclic permutation. A transposition is a cyclic permutation with a cycle size of 2.
Lemma 10.6.
The composition of permutations is a permutation.
Composition of permutations is associative.
For every permutation p on a set S, there is an inverse permutation p–1 such that p–1 p = p p–1 = identity
S.
Every finite group is a subgroup of a permutation group of its elements, where every permutation in the subgroup is generated by multiplying all the elements by an individual element. x 1 2 3 4 1 1 2 3 4 2 2 4 1 3 3 3 1 4 2 4 4 3 2 1
Lemma 10.7. Lemma 10.8. Exercise 10.2. Lemma 10.9. Lemma 10.10. Lemma 10.11. Lemma 10.12.
A finite set S of size n is a set for which there exists a pair of functions
chooseS : [0, n) S
indexS : S [0, n)
satisfying
chooseS(indexS(x)) = x indexS(chooseS(i)) = i
In other words, S can be put into one-to-one correspondence with a range of natural numbers.
If p is a permutation on a finite set S of size n, there is a corresponding index permutation p' on [0, n) defined as
Every element in a permutation belongs to a unique cycle.
Any permutation of a set with n elements contains k n cycles.
Disjoint cyclic permutations commute.
Show an example of two nondisjoint cyclic permutations that do not commute.
Every permutation can be represented as a product of the cyclic permutations corresponding to its cycles.
The inverse of a permutation is the product of the inverses of its cycles.
Every cyclic permutation is a product of transpositions.
p'(i) = index
S(p(chooseS(i)))
Lemma 10.13.
We will frequently define permutations by the corresponding index permutations. p(x) = chooseS(p'(indexS(x)))
Algorithms C++ Software Engineering Programming Alexander Stepanov Paul McJones Addison-Wesley Professional Elements of Programming
10.2. Rearrangements
A rearrangement is an algorithm that copies the objects from an input range to an output range such that the mapping between the indices of the input and output ranges is a permutation. This chapter deals with position-based rearrangements, where the destination of a value depends only on its original position and not on the value itself. The next chapter deals with predicate-based
rearrangements, where the destination of a value depends only on the result of applying a predicate to a value, and ordering-based rearrangements, where the destination of a value depends only on the ordering of values.
In Chapter 8 we studied link rearrangements, such as reverse linked, where links are modified to establish a rearrangement. In Chapter 9 we studied copying rearrangements, such as copy and reverse_copy. In this and the next chapter we study mutative rearrangements, where the input and output ranges are identical.
Every mutative rearrangement corresponds to two permutations: a to-permutation mapping an iterator i to the iterator pointing to the destination of the element at i and a from-permutation mapping an iterator i to the iterator pointing to the origin of the element moved to i.
Lemma 10.14.
If the to-permutation is known, we can rearrange a cycle with this algorithm:
template<typename I, typename F>
requires(Mutable(I) && Transformation(F) && I == Domain(F)) void cycle_to(I i, F f)
{
// Precondition: The orbit of i under f is circular // Precondition: ( n ϵ N) deref(fn(i)) is defined I k = f(i);
while (k != i) {
exchange values(i, k); k = f(k);
}
The to-permutation and from-permutation for a rearrangement are inverses of each other.
}
After cycle_to(i, f), the value of source(f(j)) and the original value of source(j) are equal for all j in the orbit of i under f. The call performs 3(n – 1) assignments for a cycle of size n.
Exercise 10.3.
If the from-permutation is known, we can rearrange a cycle with this algorithm:
template<typename I, typename F>
requires(Mutable(I) && Transformation(F) && I == Domain(F)) void cycle from(I i, F f)
{
// Precondition: The orbit of i under f is circular // Precondition: ( n ϵ N) deref(fn(i)) is defined ValueType(I) tmp = source(i); I j = i; I k = f(i); while (k != i) { sink(j) = source(k); j = k; k = f(k); } sink(j) = tmp; }
After cycle_from (i, f), the value of source(j) and the original value of source(f(j)) are equal for all j in the orbit of i under f. The call performs n + 1 assignments, whereas implementing it with exchange_values would perform 3(n – 1) assignments. Observe that we require only mutability on the type I; we do not need any traversal functions, because the transformation f performs the traversal. In addition to the from-permutation, implementing a mutative rearrangement using cycle_from requires a way to obtain a representative element from each cycle. In some cases the cycle structure and representatives of the cycles are known.
Exercise 10.4.
Exercise 10.5.
Lemma 10.15.
Implement a version of cycle_to that performs 2n – 1 assignments.
Implement an algorithm that performs an arbitrary rearrangement of a range of indexed iterators. Use an array of n Boolean values to mark elements as they are placed, and scan this array for an unmarked value to determine a representive of the next cycle.
Assuming iterators with total ordering, design an algorithm that uses constant storage to determine whether an iterator is a representative for a cycle; use this algorithm to implement an arbitrary rearrangement.
Given a from-permutation, it is possible to perform a mutative rearrangement using n +
cN – cT assignments, where n is the number of elements, cN the number of nontrivial cycles, and cT the number of trivial cycles.
Algorithms C++ Software Engineering Programming Alexander Stepanov Paul McJones Addison-Wesley Professional Elements of Programming