The Standard Library’s
stable_partition is pretty slick, but as a generalized function it cannot make some assumptions that could be significant.
For example, here are two other versions of a stable partition.
1 2 3 4 5 6 7 8 9
|
#include <algorithm>
#include <iostream>
#include <iterator>
#include <sstream>
#include <string>
#include <vector>
#define EQUIVALENCE_EQ_IDENTITY 0
//#define EQUIVALENCE_EQ_IDENTITY 1
|
Version 1: equivalence implies identity
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
#if EQUIVALENCE_EQ_IDENTITY
// This is a partition where it is recognized that
// equivalent items are IDENTICAL, meaning that any
// single x can be used to replace another.
// This is an O(n) complexity, O(1) space.
void partition( int x, int* xs, int n )
{
int* froms = xs;
int* tos = xs;
int* end = xs + n;
while (froms < end)
{
if (*froms != x) *tos++ = *froms;
++froms;
}
while (tos < end)
*tos++ = x;
}
#endif
|
Version 2: equivalence does not imply identity
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
|
#if !EQUIVALENCE_NE_IDENTITY
// This is a partition where equivalence is not
// necessarily the same as identity, meaning that
// we cannot take the shortcut we took earlier.
// We can, however, still be pretty slick about it.
// It is an O(n) complexity, O(n) space.
void partition( int y, int* xs, int n )
{
std::vector <int> ys;
int* froms = xs;
int* tos = xs;
int* end = xs + n;
while (froms < end)
{
if (*froms == y) ys.push_back( y );
else *tos++ = *froms;
++froms;
}
froms = ys.data();
while (tos < end)
*tos++ = *froms++;
}
#endif
|
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
|
// A couple helper routines to get input
std::vector <int> ask_xs()
{
std::vector <int> xs;
std::cout << "xs? ";
std::string s;
getline( std::cin, s );
std::istringstream ss( s );
std::copy(
std::istream_iterator <int> ( ss ),
std::istream_iterator <int> (),
std::back_inserter( xs )
);
return xs;
}
int ask_x()
{
std::cout << "x? ";
std::string s;
getline( std::cin, s );
return std::stoi( s );
}
// Finally! Interesting stuff...
int main()
try
{
for (auto xs = ask_xs(); !xs.empty(); xs = ask_xs())
{
auto x = ask_x();
partition( x, xs.data(), xs.size() );
for (auto x : xs)
std::cout << x << " ";
std::cout << "\n\n";
}
}
catch (...) { }
|
Remember, when we are comparing items we have an
equivalence function, like
==, that tells us whether we can consider the elements to be
equivalent.
But comparing as equivalent does not necessarily imply that they are
identical, which is to say, we cannot assume that replacing one element with an equivalent one will result in the same thing.
As the most basic example, family members can be considered equivalent in certain circumstances. “Hey, we’re having a party. Someone needs to invite the Schmidts.” In this case, it really doesn’t matter which family member you tell about the party.
However, being equivalent is not the same as identical. “Hey, can your brother drive us to the store?” “No. He doesn’t have a driver’s permit yet.” Clearly, when the police officer pulls you over he won’t pretend you are your brother. He’ll give
you a ticket.
The same goes for data. Integers can be treated identically, but many classes of objects cannot. (The reasons run deep, much deeper down the rabbit hole than you may think. Explore some memory management pools to get there.)
Also, remember that a
std::vector is a variable-length array replacement.
Were I using an array, I would have written:
1 2
|
int xs[ SOME_BIG_NUMBER ];
int xs_size = 0;
|
The problem with arrays is, of course, that I suddenly have to keep track of all kinds of stuff I really don’t want to —and vectors will do it for me for free! Yaay!
Hope this helps.