Another STL question

Started by
23 comments, last by Fruny 17 years, 11 months ago
Quote:Original post by Wiggin
I guess the lesson here is not to oversimplify ones problem when asking for help.

I think I'm beginning to see the light... almost.

So when I add a partition with no moving object, I append it, and when I add a partition with, I prepend it, and the iterator firstWithoutMovingObjects will remain valid, right?


Using a std::list, yes. Of course, redoing the partition()ing makes that irrelevant.

Then again, I wrote that with the assumption that a given Partition's hasMovingObjects() might change over time. If not, then of course you'd just need to make two separate lists :)

Quote:What about when I, due to objects moving out of the area, need to delete some smaller partitions and replace them with a bigger one?


You can 'remove' the partitions that compose the new one (assuming you have a function that can identify them), and then just insert the new one:

// The std algorithms expect a "predicate" with a specific set of arguments.// In our case, we don't have a way to specify which Partition we're looking// "within" directly from the algorithm. However, we're not just limited to// function pointers: we can use functors, i.e. structs/classes which implement// the operator() with an appropriate argument list and return type.// Then we construct one which "holds onto" a Partition, and refers to it when// doing its calculations.struct isWithin {  Partition* container; // non-owning pointer; don't delete!  // That should really be a reference, but I tend to mess up using references  // as data members...  // We'll just make a constructor and the operator overload.  isWithin(const Partition& container): container(&container) {}  bool operator() (const Partition& p) {    return p.isWithin(*container);  }};//...// These functions are also in <algorithm>.std::erase(std::remove_if(partitions.begin(),                           partitions.end(),                           isWithin(theBigOne)), partitions.end());


Alternatively, set up the predicate so that it directly recognizes which things you want to remove, and partition the list into the about-to-be-removed ones and the rest. Then you can use the "range" of about-to-be-removed ones to construct the big partition, erase() that range, and insert the new one.

// Example of constructing big partition from the range.template <typename ForwardIterator>Partition::Partition(ForwardIterator begin, ForwardIterator end) {  doBasicConstruction();  for (ForwardIterator component = begin; component != end; ++component) {    union(*component); // "add" each component in turn to self.  }}// Now you can pass in two iterators - for example, begin() and the result of // the partition() call (or the call result and end(), depending on the sense// of the partitioning.

Advertisement
It's obvious there are some aspects off c++ I have yet to familiarize myself with. I'm off to experiment. Many many thanks.
Incidentally, if each partition knows whether it contains moving objects or not, you can use the std::partition [rolleyes][lol] algorithm to separate them.
#include <functional> // For std::mem_fun#include <algorithm>  // For std::partition#include <list>       // For std::listclass Partition{public:   bool has_moving_objects() const; // Defined somewhere...};std::list<Partition*> parts;  // Filled somewhere...std::list<Partition*>::iterator nonmoving;nonmoving = std::partition(parts.begin(), parts.end(),                           std::mem_fun(&Partition::has_moving_objects));


Partitions in [begin; nonmoving) will have moving objects.
Partitions in [nonmoving; end) won't have moving objects.

nonmoving is an iterator to the first partition without moving objects.

It's a bit too expensive to call every time you add a new partition (it's O(N)), but it's handy to have around, just in case you need to do some major reorganization of the list's contents.

Of course, if you make sure to always insert the partitions in the right place in the list, there shouldn't be a problem... you preserve your invariant.


If you want to merge two lists of partitions, you can do
std::list<Partition*> part1, part2;                     // Filled somewhere...std::list<Partition*>::iterator nonmoving1, nonmoving2; // Initialized somewhere...part1.splice(nonmoving1, part2, part2.begin(), nonmoving2);part1.splice(part1.end(), part2, nonmoving2, part2.end());


This will inject [begin2; nonmoving2) before nonmoving1, and [nonmoving2; end2) at the end of part1 -- the invariant is still preserved.

Note that after this, part2 is empty: its contents (the actual list nodes) have literally been moved over to part1, which is why this operation is fast (potentially constant time, depending on some library design decisions, O(N) otherwise).
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
Quote:Original post by Wiggin
It's obvious there are some aspects off c++ I have yet to familiarize myself with.


Don't worry, you're in good - er, well, lots of company. :D

Fruny: Yes, that's more or less what I was trying to get at. Thanks for the summary, and also the reminder about std::mem_fun_ref(). The partitioning is in any case likely to be faster than whatever was being done before with manual manipulation of stuff ;s

Just to explain about splice() being maybe-fast: there are two obvious ways to implement .size() for a list - you can count the items every time that's called, or you can keep a member variable in the list "header" which keeps a cached count of the items. If you have that cached value, you have a fast .size(), but you cannot have a fast .splice(), because in order to make that cached value stay correct, you have to count the nodes that are being added, which means *traversing* that list of to-be-inserted things. Otherwise, you have a slow .size() (obviously), but .splice() can just manipulate a fixed number of pointers at each end of the splice-chunk. (For the comp-sci minded, in the above, "fast" and "slow" mean O(1) and O(N) respectively.)
Quote:Original post by Zahlman
and also the reminder about std::mem_fun_ref()


std::mem_fun() -- he's got a container of pointers.

Quote:Just to explain about splice() being maybe-fast


FWIW, in the implementation that ships with Visual Studio, size() is fast, splice() is fast if the source list is the same as the target list, and slow otherwise.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan

This topic is closed to new replies.

Advertisement