Search code examples
c++combinations

in C++, looping through all k elements of a vector


In C++, I have a vector of int, such that each element is a n-bits integer.

I would like to know the most efficient way to loop through all distinct k elements of the vector. i.e. I want to loop through all combinations of k elements of my vector. What is the 'best' way to do it ?

When k is fixed and know, one way to do it is with nested for loops, as below, but the approach is not really nice, not scalable to higher k, and probably not the most efficient.

Example1 with k=2, using two nested for loops.

#include <iostream>
#include <vector>

using namespace std;

int main() {
   vector<int> vect={1,8,9,10};
   for (int i:vect){
      for (int j:vect){
         //do something;
      }
   }
   return 0;
}

Example2: Using the fact that the element are distinct, I can loop through indices, and make it much faster when the vector is large.

#include <iostream>
#include <vector>

using namespace std;

int main() {
   vector<int> vect={1,8,9,10};
   size_t vect_size = vect.size();
   for (size_t a = 0; a != vect_size ; a++) {
      for (size_t b = a+1; b != vect_size ; b++) {
         i = vect[a];
         j = vect[b];
         //do something
      }
   }
   return 0;
}

I tried to use the itertools package (ported from python to C++), but it's not working on my laptop, not sure why yet. So I would like to scale that type of code to any k.


Solution

  • The more or less standard approach to solve this problem is to use a selector and permute over the selector.

    Example: Assume, you have a std::vector with five elements. Then you need to create a helper std::vector<bool> selector and set k elements to true. Do a permutation over the selector and then copy the data, with a "true" corresponding selector into the result:

    5 Values: k= 2
    
    Values    1 2 3 4 5
    Selector  0 0 0 1 1
    
    Store selected data 4 and 5. 
    Get next permutation for selector
    
    Values    1 2 3 4 5
    Selector  0 0 1 0 1
    
    Store selected data 3 and 5. 
    Get next permutation for selector
    
    Values    1 2 3 4 5
    Selector  0 0 1 1 0
    
    Store selected data 3 and 4. 
    Get next permutation for selector
    
    Values    1 2 3 4 5
    Selector  0 1 0 0 1
    
    Store selected data 2 and 5. 
    Get next permutation for selector . . .
    
    And so on and so on . . .
    

    An additional requirement was to have distinct results. We will achieve this by using a std::set which can hold only unique values.

    The implementation is the straight forward and easy to understand. Please see the below example:

    #include <iostream>
    #include <vector>
    #include <algorithm>
    #include <set>
    #include <iterator>
    
    std::set<std::set<int>> getCombinations(const std::vector<int>& v, int k) {
    
        std::set<std::set<int>> result{};
    
        // Create the selector array
        std::vector<bool> selector(v.size(), false);
    
        // Set k elements in the selector array to true
        std::fill(selector.begin(), std::next(selector.begin(), k), true);
    
        // Now we will do all permutations of the selector
        do {
            // Temporary, to store one group of selected data
            std::set<int> group{};
    
            // Copy data into group, if corresponding selector is set
            std::copy_if(v.begin(), v.end(), std::inserter(group,group.begin()), [&, k = 0u](int i)mutable { return selector[k++]; });
    
            // Only store data, with the requested size of k
            if (group.size() == k)
                result.insert(group);
    
        } while (std::prev_permutation(selector.begin(), selector.end()));
    
        return result;
    }
    
    // Test code for all possible ks
    int main() {
        std::vector data{ 1,2,3,3,3,4,5 };
        
        for (int k = 1; k <= data.size(); ++k) {
            auto combinations = getCombinations(data, k);
            std::cout << "\n\nFor k = " << k << "\n";
            for (const auto& v : combinations) {
                for (const int i : v) std::cout << i << ' ';
                std::cout << '\n';
            }
        }
    }
    

    Result:

    For k = 1
    1
    2
    3
    4
    5
    
    
    For k = 2
    1 2
    1 3
    1 4
    1 5
    2 3
    2 4
    2 5
    3 4
    3 5
    4 5
    
    
    For k = 3
    1 2 3
    1 2 4
    1 2 5
    1 3 4
    1 3 5
    1 4 5
    2 3 4
    2 3 5
    2 4 5
    3 4 5
    
    
    For k = 4
    1 2 3 4
    1 2 3 5
    1 2 4 5
    1 3 4 5
    2 3 4 5
    
    
    For k = 5
    1 2 3 4 5
    
    
    For k = 6
    
    
    For k = 7
    

    IF you also want to have duplicated vales, then replace the std::set with a std::vector

    #include <iostream>
    #include <vector>
    #include <algorithm>
    #include <set>
    #include <iterator>
    
    std::set<std::vector<int>> getCombinations(const std::vector<int>& v, int k) {
    
        std::set<std::vector<int>> result{};
    
        // Create the selector array
        std::vector<bool> selector(v.size(), false);
    
        // Set k elements in the selector array to true
        std::fill(selector.begin(), std::next(selector.begin(), k), true);
    
        // Now we will do all permutations of the selector
        do {
            // Temporary, to store one group of selected data
            std::vector<int> group{};
    
            // Copy data into group, if corresponding selector is set
            std::copy_if(v.begin(), v.end(), std::back_inserter(group), [&, k = 0u](int i)mutable { return selector[k++]; });
    
            // store data, with the requested size of k
            result.insert(group);
    
        } while (std::prev_permutation(selector.begin(), selector.end()));
    
        return result;
    }
    
    

    Result:

    For k = 1
    1
    2
    3
    4
    5
    
    
    For k = 2
    1 2
    1 3
    1 4
    1 5
    2 3
    2 4
    2 5
    3 3
    3 4
    3 5
    4 5
    
    
    For k = 3
    1 2 3
    1 2 4
    1 2 5
    1 3 3
    1 3 4
    1 3 5
    1 4 5
    2 3 3
    2 3 4
    2 3 5
    2 4 5
    3 3 3
    3 3 4
    3 3 5
    3 4 5
    
    
    For k = 4
    1 2 3 3
    1 2 3 4
    1 2 3 5
    1 2 4 5
    1 3 3 3
    1 3 3 4
    1 3 3 5
    1 3 4 5
    2 3 3 3
    2 3 3 4
    2 3 3 5
    2 3 4 5
    3 3 3 4
    3 3 3 5
    3 3 4 5
    
    
    For k = 5
    1 2 3 3 3
    1 2 3 3 4
    1 2 3 3 5
    1 2 3 4 5
    1 3 3 3 4
    1 3 3 3 5
    1 3 3 4 5
    2 3 3 3 4
    2 3 3 3 5
    2 3 3 4 5
    3 3 3 4 5
    
    
    For k = 6
    1 2 3 3 3 4
    1 2 3 3 3 5
    1 2 3 3 4 5
    1 3 3 3 4 5
    2 3 3 3 4 5
    
    
    For k = 7
    1 2 3 3 3 4 5