i've a graph class which has two std::map in it; the maps are private, and i want the user to only be able to loop through both maps, not modifying them.
The point is, (well the first point is i never wrote a standard iterator) they have to look like there's only one map from outside.
so that a code that looks like:
for (auto element : stuff)
{
//do stuff
}
will actually do that:
for (auto element : map_1)
{
}
for (auto element : map_2)
{
}
How do i manage the step between the first map and the second one?
Within your custom iterator, store two fields:
struct example {
unsigned map_index;
map_iterator it;
};
map_index tells you which map is being iterated.
In the increment operator after incrementing it, if it == map_0.end() (I changed the numbering to start from zero) then increment map_index and set it to map_1.begin().
Use {map_1.end(), 1} as the end iterator.
In the comparison operators, compare map_index first and only compare it if the indices match.
That said, you can save yourself a lot of work by using existing, generic functionality: boost::range::join
Related
I've seen other post but I am trying to do this using some of the <algorithm> methods. I have a pointer to a map which contains a key to a vector of pointers to BaseElement classes like the following.
using ElementV = std::vector<std::shared_ptr<BaseElement>>;
using ElementM = std::map<int, ElementV>;
using SElementM = std::shared_ptr<ElementM>;
SElementM elements;
What I am trying to do is concatenate each of the vectors (ElementV) stored as values in the map (ElementM) and populate one large ElementV. I'd like to not have to do deep copies and just access the original elements by their smart pointers (shared_ptr(BaseElement)) from the allElems vector.
The following is wrong, but gives the idea of what I'm trying to do.
ElementV allElems;
for (auto& index : indices) {
allElems = elements->at(index);
}
I suspect I should be using lambas with std::copy, but have been unable to get something similar to the following to work and I think it's because of iterators.
std::copy(allElems.begin(), allElems.end(), [const elements &elems](std::vector<shared_ptr<BaseElement> &val) {
elems ...?
}
Thoughts?
You can get pairs of keys (first) and values (second) via iteraters of std::map, so inserting each vectors via std::for_each is one way.
ElementV allElems;
std::for_each(elements->begin(), elements->end(), [&allElems](const auto& p) {
allElems.insert(allElems.end(), p.second.begin(), p.second.end());
});
You could try this:
ElementV allElems;
//assuming c++17 compiler
for(auto& [ind, elemVectorPtr]: *elements){ //iterate through index, element-vector pointer pair in elements map
//copy across all pointers in element-vector into allElems vector
std::copy(elemVectorPtr->begin(), elemVectorPtr->end(), std::back_inserter(allElems));
}
If you don't have a c++17 compiler, just iterate directly through pairs of items in the map:
for(auto& pair : *elements){
std::copy(pair.second->begin(), pair.second->end(), std::back_inserter(allElems));
}
I have a boost::multi_index with a hashed_non_unique view. What I would like to accomplish is, given a key in that view, use
pair<myIter, myIter> iterRange = myView.equal_range(key);
for (myIter iter = iterRange.first; iter != iterRange.second; ++iter) {
// ...
}
to find all elements associated with that key. Then, run these elements through a filter
bool filter(Element e) { /* some filtering logic*/ }
and modify the filtered results with a modifier
void modifier(Element e) { /* modify the elements with e.g. myView.modify() */ }
However, simply putting these pieces together doesn't work since modifying the elements results in reordering of the multi_index, which renders my iterRange invalid.
What would be the correct way to do this? Thanks!
Some comments to your proposed solution:
BMIter is not special as you seem to imply, but merely the iterator associated to the first index of the container. Note that this will be the same as myIter when myView happens to be this first index.
Nevertheless, iterators of hashed indices are not invalidated by insertions or modifications, so you're safe. In fact, you could have defined iters as a vector<myIter> and store the iterators directly without any further conversion --you're still achieving the intended effect of not being affected by potential reorderings after modification.
Even though what you're doing is perfectly fine, if you're into squeezing some additional performance note that modification of elements in a hashed index does not change the underlying ordering when the key remains equivalent, so the only way a reordering can possibly affect you when traversing a range of equivalent keys is when a modified element jumps directly after the range (i.e, just before iterRange.second). With this mind, you can spare the iters trick as follows:
for (myIter iter = iterRange.first; iter != iterRange.second; ) {
auto nextIter = std::next(iter);
if (filter(*iter)) {
myView.modify(iter, modifier);
if (nextIter != iterRange.second && std::next(iter) == iterRange.second)
iterRange.second = iter;
}
iter = nextIter;
}
I think I've found the answer myself. Instead of simply modifying the elements inside the for loop, we need to cache the elements first and modify them later to avoid altering the ordering. The trick here is, instead of caching the iterators to this specific view, cache instead iterators to the elements themselves, i.e.
vector<BMIIter> iters;
for (myIter iter = iterRange.first; iter != iterRange.second; ++iter) {
if (filter(*iter)) {
iters.push_back(myBMI.iterator_to(*iter));
}
}
for (auto iter : iters) {
myBMI.modify(iter, modifier);
}
Note that BMIIter and myIter are different iterator types - the former is an iterator to the element itself, while the latter is iterator specific to myView. Modifying elements in the multi_index invalidates the latter, but the former still holds valid even after reordering happened.
Full disclosure, this may be a hammer and nail situation trying to use STL algorithms when none are needed. I have seen a reappearing pattern in some C++14 code I am working with. We have a container that we iterate through, and if the current element matches some condition, then we copy one of the elements fields to another container.
The pattern is something like:
for (auto it = std::begin(foo); it!=std::end(foo); ++it){
auto x = it->Some_member;
// Note, the check usually uses the field would add to the new container.
if(f(x) && g(x)){
bar.emplace_back(x);
}
}
The idea is almost an accumulate where the function being applied does not always return a value. I can only think of a solutions that either
Require a function for accessing the member your want to accumulate and another function for checking the condition. i.e How to combine std::copy_if and std::transform?
Are worse then the thing I want to replace.
Is this even a good idea?
A quite general solution to your issue would be the following (working example):
#include <iostream>
#include <vector>
using namespace std;
template<typename It, typename MemberType, typename Cond, typename Do>
void process_filtered(It begin, It end, MemberType iterator_traits<It>::value_type::*ptr, Cond condition, Do process)
{
for(It it = begin; it != end; ++it)
{
if(condition((*it).*ptr))
{
process((*it).*ptr);
}
}
}
struct Data
{
int x;
int y;
};
int main()
{
// thanks to iterator_traits, vector could also be an array;
// kudos to #Yakk-AdamNevraumont
vector<Data> lines{{1,2},{4,3},{5,6}};
// filter even numbers from Data::x and output them
process_filtered(std::begin(lines), std::end(lines), &Data::x, [](int n){return n % 2 == 0;}, [](int n){cout << n;});
// output is 4, the only x value that is even
return 0;
}
It does not use STL, that is right, but you merely pass an iterator pair, the member to lookup and two lambdas/functions to it that will first filter and second use the filtered output, respectively.
I like your general solutions but here you do not need to have a lambda that extracts the corresponding attribute.
Clearly, the code can be refined to work with const_iterator but for a general idea, I think, it should be helpful. You could also extend it to have a member function that returns a member attribute instead of a direct member attribute pointer, if you'd like to use this method for encapsulated classes.
Sure. There are a bunch of approaches.
Find a library with transform_if, like boost.
Find a library with transform_range, which takes a transformation and range or container and returns a range with the value transformed. Compose this with copy_if.
Find a library with filter_range like the above. Now, use std::transform with your filtered range.
Find one with both, and compose filtering and transforming in the appropriate order. Now your problem is just copying (std::copy or whatever).
Write your own back-inserter wrapper that transforms while inserting. Use that with std::copy_if.
Write your own range adapters, like 2 3 and/or 4.
Write transform_if.
I want to make use of libstdc++'s __gnu_parallel::multiway_merge to merge four large sequences of sorted key-value pairs at once (to save memory bandwidth).
Each sequence of key-value pairs is represented by two distinct arrays, such that
values[i] is the value associated with keys[i].
The implementation for multiway-merging a single array of keys (keys-only) or an array of std::pairs would be straightforward. However, I need to implement a custom iterator that I can pass to the multiway_merge, which holds one reference to my keys array and one to the corresponding values array.
So my approach looks something like the following:
template<
typename KeyT,
typename ValueT
>
class ForwardIterator : public std::iterator<std::forward_iterator_tag, KeyT>
{
KeyT* k_itr;
ValueT* v_itr;
size_t offset;
explicit ForwardIterator(KeyT* k_start, ValueT *v_start) : k_itr(k_start), v_itr(v_start), offset(0)
{
}
ForwardIterator& operator++ () // Pre-increment
{
offset++;
return *this;
}
}
However, the problems start as soon as I'm getting to the overloading of the dereferencing operator.
Help is really appreciated! Thanks!
I need to add, store and delete some pairs of objects, e.g. Person-Hobby. Any person can have several hobbies and several persons can have the same hobby. So, multimap is a good container, right?
Before adding a pair I need to know, if it's not added yet. As I can see here there is no standard class-method to know, if the concrete pair e.g. Peter-Football exists in the MM. Thus, I've written a method which returns a positive integer (equal to the distance between mm.begin() and pair iterator) if the pair exists and -1 otherwise.
Then I need to delete some pair. I call my find method, which returns, some positive integer. I call myMultiMap.erase(pairIndex); but the pair is not being deleted for some reason. That is my problem. Obviously the erase method needs an iterator, not the int. The question is: how do I convert an integer to an iterator?
Thanks!
UPDATE:
I've tried this c.begin() + int_value but got an error error: no match for ‘operator+’ on this line....
Not that I favour your approach, but if the int is distance between begin() and the iterator in question, you can just use
c.begin() + int_value
or
std::advance(c.begin(), int_value)
to get the iterator. The second version is needed for iterators which are not random-access-iterators.
In the interest of your personal sanity (and the program's speed), I'd suggest you return the iterator directly in some form.
There are many possible interfaces that solve this one way or the other. What I would call the "old C way" would be returning by an out parameter:
bool find_stuff(stuff, container::iterator* out_iter) {
...
if(found && out_iter)
*out_iter = found_iter;
return found;
}
use it:
container::iterator the_iter;
if(find_stuff(the_stuff, &the_iter)) ...
or
if(find_stuff(the_stuff, 0)) // if you don't need the iterator
This is not idiomatic C++, but Linus would be pleased with it.
The second possible and theoretically sound version is using something like boost::optional to return the value. This way, you return either some value or none.
boost::optional<container::iterator> find_stuff(stuff) {
...
if(found && out_iter)
return found_iter;
return boost::none;
}
Use:
boost::optional<container::iterator> found = find_stuff(the_stuff);
if(found) {
do something with *found, which is the iterator.
}
or
if(find_stuff(the_stuff)) ...
Third possible solution would be going the std::set::insert way, ie. returning a pair consisting of a flag and a value:
std::pair<bool, container::iterator> find_stuff(stuff) {
...
return std::make_pair(found, found_iter);
}
Use:
std::pair<bool, container::iterator> found = find_stuff(the_stuff);
if(found.first) ...
Consider to change your mulitmap<Person,Hoobby> to set<pair<Person,Hobby> > - then you will not have problems you have now. Or consider to change to map<Person, set<Hobby> >. Both options will not allow to insert duplicate pairs.
Use 2 sets(not multi sets) one for hobbies and one for persons, these two acts as filters so you don't add the same person twice(or hobbie). the insert opertations on these sets gives the iterator for the element that is inserted(or the "right" iterator for element if it allready was inserted). The two iterators you get from inserting into hobbies_set and person_set are now used as key and value in a multimap
Using a third set(not multi_set) for the relation instead of a multi_map, may give the advantage of not needing to check before inserting a relation if it is allready there it will not be added again, and if it's not there it will be added. In both ways it will return an iterator and bool(tells if it was allready there or if it was added)
datastructures:
typedef std::set<Hobbie> Hobbies;
typedef std::set<Person> Persons;
typedef std::pair<Hobbies::iterator,bool> HobbiesInsertRes;
typedef std::pair<Persons::iterator,bool> PersonsInsertRes;
struct Relation {
Hobbies::iterator hobbieIter;
Persons::iterator personIter;
// needed operator<(left for the as an exercies for the reader);
};
typedef set<Relation> Relations;
Hobbies hobbies;
Persons persons;
Relations relations;
insert:
HobbiesInsertRes hres = hobbies.insert(Hobbie("foo"));
PersonsInsertRes pres = persons.insert(Person("bar"));
relations.insert(Relation(hres.first, pres.first));
// adds the relation if does not exists, if it allready did exist, well you only paid the same amount of time that you would have if you would to do a check first.
lookup:
// for a concrete Person-Hobbie lookup use
relations.find(Relation(Hobbie("foo"),Person("Bar")));
// to find all Hobbies of Person X you will need to do some work.
// the easy way, iterate all elements of relations
std::vector<Hobbie> hobbiesOfX;
Persons::iterator personX = persons.find(Person("bar"));
std::for_each(relations.begin(), relations.end(), [&hobbiesOfBar, personX](Relation r){
if(r.personIter = personX)
hobbiesOfX.push_back(r.hobbieIter);
});
// other way to lookup all hobbies of person X
Persons::iterator personX = persons.find(Person("bar"));
relations.lower_bound(Relation(personX,Hobbies.begin());
relations.upper_bound(Relation(personX,Hobbies.end());
// this needs operator< on Relation to be implemented in a way that does ordering on Person first, Hobbie second.