0

Suppose I have a sorted vector without duplicated values. If I want to go through all the triples of different values, I do this:

for(std::size_t i = 0; i < data.size(); ++i)
  for(std::size_t j = i+1; j < data.size(); ++j)
    for(std::size_t k = j+1; k < data.size(); ++k)
      do_somthing_with(data[i],data[j],data[k]);

How can I do that with iterators if my container is a std::set ?

Note: I don't use C++11 for compatibility reasons.

Caduchon
  • 4,171
  • 2
  • 22
  • 62
  • how would you write a single loop to iterate over elements in a `set` ? – 463035818_is_not_a_number Aug 27 '18 at 12:42
  • @user463035818 : I do a single loop by this way : `for(std::set::const_iterator it = data.begin(); it != data.end(); ++it) do_something();` – Caduchon Aug 27 '18 at 12:43
  • @Caduchon You can do the exact same thing you do in your nested loops over the vector. `for (std::set::const_iterator it = data.begin(); it != data.end(); ++it) for (std::set::const_iterator jt = it + 1; jt != data.end(); ++jt) ...` . This possibly is a more effective solution then messing with bitsets, as std::set does not provide random access iterators. – paler123 Aug 27 '18 at 12:58
  • @paler123 : `it + 1` is only available for random-access-iterator. Here it's a bidirectional-iterator. – Caduchon Aug 27 '18 at 13:05
  • Oh, my bad. You'll need to create a wrapper function that will copy the iterator and increment it for this solution to work. – paler123 Aug 27 '18 at 13:16

5 Answers5

1

you might do something like:

if (data.size() < 2) { return; }

for (auto it1 = data.begin(); it1 != std::prev(data.end(), 2); ++it1) {
    for (std::size_t it2 = std::next(it1); it2 != std::prev(data.end()); ++it2) {
      for (std::size_t it3 = std::next(it2); it3 != data.end(); ++it3) {
          do_something_with(*it1, *it2, *it3);
      }
   }
}

You might cache value of std::prev.

Jarod42
  • 173,454
  • 13
  • 146
  • 250
1

You can do pretty much the same as with the vector, but you'll need to create a wrapper function, that will copy and increment set iterator:

std::set<int>::const_iterator next_iterator(std::set<int>::const_iterator it)
{
  return ++it; // it has been passed by value, so already copied
}

//...

for (std::set<int>::const_iterator it = data.begin(); it != data.end(); ++it)
  for(std::set<int>::const_iterator jt = next_iterator(it); jt != data.end(); ++jt)
    for(std::set<int>::const_iterator kt = next_iterator(jt); kt != data.end(); ++kt)
       // ...
paler123
  • 956
  • 6
  • 17
0

This is a special case of choosing all k-combinations out of a set of n elements.

See here for an explanation: https://en.wikipedia.org/wiki/Combination

And this has been answered on stackoverflow before here: creating all possible k combinations of n items in C++

hassec
  • 636
  • 4
  • 16
  • this isnt really an answer, as answers should be selfcontained by at least summarizing the important parts. If you think the question is a duplicate, then you can flag it as duplicate – 463035818_is_not_a_number Aug 27 '18 at 13:07
0

You can think of your problem as if you had to use iterators over indexes in your vector example. Let's simplify it that way:

for(std::size_t i = 0; i < data.size(); ++i)
  do_somthing_with(data[i]);

You would write it like that with iterators:

for(std::vector<MyClass>::iterator it = data.begin(); it != data.end(); ++it)
  do_somthing_with(*it);

Now it is straightforward to do the same with sets:

for(std::set<MyClass>::iterator it = data.begin(); it != data.end(); ++it)
  do_somthing_with(*it);

If you want to use a triple-loop, in my opinion the problem is starting at the next iterator. With C++11 you could use std::next but since you can't, you need to store the iterator and then advance it:

for(std::set<MyClass>::iterator it1 = data.begin(); it1 != data.end(); ++it1)
{
  std::set<MyClass>::iterator it2 = it1;
  ++it2;
  for(; it2 != data.end(); ++it2)
  {
    std::set<MyClass>::iterator it3 = it2;
    ++it3;
    for(; it3 != data.end(); ++it3)
      do_somthing_with(*it1,*it2,*it3);
  }
}

Alternately you can define your own next function, e.g.:

template<class ForwardIt>
ForwardIt next(ForwardIt it)
{
    return ++it;
}

This is basically a simplified version of the suggested implementation of std::next.

Then the code can be rewritten more easily:

for(std::set<MyClass>::iterator it1 = data.begin(); it1 != data.end(); ++it1)
  for(std::set<MyClass>::iterator it2 = next(it1); it2 != data.end(); ++it2)
    for(std::set<MyClass>::iterator it3 = next(it2); it3 != data.end(); ++it3)
      do_somthing_with(*it1,*it2,*it3);
vdavid
  • 1,751
  • 9
  • 14
-1

You could create a vector from it and then do it the way you usually do it.

std::set<int> s = {1, 2, 3, 4, 5, 6, 7};
std::vector<int> data(s.begin(), s.end());
for(std::size_t i = 0; i < data.size(); ++i)
  for(std::size_t j = i+1; j < data.size(); ++j)
    for(std::size_t k = j+1; k < data.size(); ++k)
      do_somthing_with(data[i],data[j],data[k]);
Jim Nilsson
  • 683
  • 3
  • 11
  • Copying the data such that it is guaranteed to be stored sequentially in memory and then iterating through could be faster than iterating through the set several times due to cache effects. The way std::set stores its data is not specified in the standard but it not likely that all elements are stored sequentially in memory. Although you would have to test this yourself for your particular use case. – Jim Nilsson Aug 27 '18 at 13:39
  • Maybe with `int`. But it's clearly not the case with big classes or classes requireing unicity. – Caduchon Aug 27 '18 at 13:53
  • Moreover, most implementation of ´std::set´ use the red-black tree algorithm which is not sequencially. – Caduchon Aug 27 '18 at 13:55