I have a set of data which is split into two arrays (let\'s call them data and keys). That is, for any given item with an index i, I c
This problem really got me thinking. I came up with a solution that makes use of some C++0x features to get a very STL-like parallel_sort algorithm. In order to perform the sort "in-place", I had to write a back_remove_iterator as the counterpart of back_insert_iterator to allow the algorithm to read from and write to the same container. You can skip over those parts and go straight to the interesting stuff.
I haven't put it through any hardcore testing, but it seems reasonably efficient in both time and space, principally due to the use of std::move() to prevent unnecessary copying.
#include
#include
#include
#include
//
// An input iterator that removes elements from the back of a container.
// Provided only because the standard library neglects one.
//
template
class back_remove_iterator :
public std::iterator {
public:
back_remove_iterator() : container(0) {}
explicit back_remove_iterator(Container& c) : container(&c) {}
back_remove_iterator& operator=
(typename Container::const_reference value) { return *this; }
typename Container::value_type operator*() {
typename Container::value_type value(container->back());
container->pop_back();
return value;
} // operator*()
back_remove_iterator& operator++() { return *this; }
back_remove_iterator operator++(int) { return *this; }
Container* container;
}; // class back_remove_iterator
//
// Equivalence operator for back_remove_iterator. An iterator compares equal
// to the end iterator either if it is default-constructed or if its
// container is empty.
//
template
bool operator==(const back_remove_iterator& a,
const back_remove_iterator& b) {
return !a.container ? !b.container || b.container->empty() :
!b.container ? !a.container || a.container->empty() :
a.container == b.container;
} // operator==()
//
// Inequivalence operator for back_remove_iterator.
//
template
bool operator!=(const back_remove_iterator& a,
const back_remove_iterator& b) {
return !(a == b);
} // operator!=()
//
// A handy way to default-construct a back_remove_iterator.
//
template
back_remove_iterator back_remover() {
return back_remove_iterator();
} // back_remover()
//
// A handy way to construct a back_remove_iterator.
//
template
back_remove_iterator back_remover(Container& c) {
return back_remove_iterator(c);
} // back_remover()
//
// A comparison functor that sorts std::pairs by their first element.
//
template
struct sort_pair_by_first {
bool operator()(const std::pair& a, const std::pair& b) {
return a.first < b.first;
} // operator()()
}; // struct sort_pair_by_first
//
// Performs a parallel sort of the ranges [keys_first, keys_last) and
// [values_first, values_last), preserving the ordering relation between
// values and keys. Sends key and value output to keys_out and values_out.
//
// This works by building a vector of std::pairs, sorting them by the key
// element, then returning the sorted pairs as two separate sequences. Note
// the use of std::move() for a vast performance improvement.
//
template
void parallel_sort(I keys_first, I keys_last, J values_first, J values_last,
K keys_out, L values_out) {
typedef std::vector< std::pair > Pairs;
Pairs sorted;
while (keys_first != keys_last)
sorted.push_back({std::move(*keys_first++), std::move(*values_first++)});
std::sort(sorted.begin(), sorted.end(), sort_pair_by_first());
for (auto i = sorted.begin(); i != sorted.end(); ++i)
*keys_out++ = std::move(i->first),
*values_out++ = std::move(i->second);
} // parallel_sort()
int main(int argc, char** argv) {
//
// There is an ordering relation between keys and values,
// but the sets still need to be sorted. Sounds like a job for...
//
std::vector keys{0, 3, 1, 2};
std::vector values{"zero", "three", "one", "two"};
//
// parallel_sort! Unfortunately, the key and value types do need to
// be specified explicitly. This could be helped with a utility
// function that accepts back_remove_iterators.
//
parallel_sort
(back_remover(keys), back_remover>(),
back_remover(values), back_remover>(),
std::back_inserter(keys), std::back_inserter(values));
//
// Just to prove that the mapping is preserved.
//
for (unsigned int i = 0; i < keys.size(); ++i)
std::cout << keys[i] << ": " << values[i] << '\n';
return 0;
} // main()
I hope this proves useful, or at least entertaining.