1

I'm trying to implement cartesian product using a closure / custom function, the closure is function(x,y) = pow(x,2) + pow(y,2) and implement it the functional way i.e. without using the C-style loops.

Here is my take.

#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
void print (vector<int> aux) {
  vector<int>::iterator it = aux.begin();
  while(it != aux.end()){
    cout << *it << " ";
    it++; }}

int main() {
  vector<int> a{1,2};
  vector<int> b{3,4};
  vector<int> cartesian(4);
  transform (a.begin(), a.end(), b.begin(), cartesian.begin(), [](int &i)
         {vector<int>::iterator p = b.begin();
           for_each(begin(b), end(b), [](int &n) { return pow(i,2) + pow(n,2) })});
  print(cartesian);
  // desired output is: 10,17,13,20 (cartesian operation)
}

First, using each element of the first vector, iterate over the second vector , then store the result of applying the function in the result vector.

The code is for representation purposes. It doesn't compile. It gives 'b' is not captured in {vector<int>::iterator p = b.begin(); error, among others.

How to rectify this code and/or how to correctly implement cartesian operation with a closure?

Lars Malmsteen
  • 738
  • 4
  • 23
  • 1
    Are you constrained to c++17? – cigien Oct 17 '20 at 18:00
  • If you're going to be using the overload of `transform` that zips up two input containers, doesn't the function you use have to be a binary operation? – Nathan Pierson Oct 17 '20 at 18:03
  • 3
    Concerning `pow(x, 2)`: I would prefer `x * x` (even if `x` would not be an integral type). FYI: [SO: Why is pow(int, int) so slow?](https://stackoverflow.com/q/41072787/7478597). – Scheff's Cat Oct 17 '20 at 18:04
  • @cigien Any version of c++11 up to c++20 is okay. As long as it uses the vector iterating / functional features it's okay. – Lars Malmsteen Oct 17 '20 at 19:21
  • Ah, so you're not including c++20? But I'm not sure what you mean by "*iterating / functional features*". I take it the answer I posted is not what you're looking for. Do you want to use ``s? – cigien Oct 17 '20 at 19:23
  • @NathanPierson depending on the construction you use, the transforming function might as well be binary. As long as the construction uses the c++11 to c++17 style functions in contrast to classical C and C++98 style for loops, it's okay. – Lars Malmsteen Oct 17 '20 at 19:25
  • @cigien yes by the vector-iterating / functional features I've tried to mean the so-called `` I'm not much familiar with the c++ specific names; algorithm, being one of them. And yes in this case the initial answer you 've posted, however neat and concise it may be, is unfortunately not what I was looking for :( – Lars Malmsteen Oct 17 '20 at 19:30
  • Ok, I've updated my answer to show options I think you're looking for. I couldn't resist showing c++20 ranges as well :) – cigien Oct 17 '20 at 19:45
  • Yes that kind of solution is exactly what I was looking for as it makes use of c++11, or 14 or 17 specific features. I haven't used c++ for the last 8 years so I'm not sure when those features were added. The c++20 specific solution is welcome too. – Lars Malmsteen Oct 17 '20 at 20:02

1 Answers1

3

Your compilation issues are because you are not capturing the needed variables in your lambdas, and you're missing some ;.

However, a simpler way to do a cartesian product is with 2 nested loops, like this:

for (int i : a)
  for (int j : b)
    cartesian.push_back(i * i + j * j);

If you want to use algorithms, then you can write:

for_each(begin(a), end(a), [&](int i) { 
  for_each(begin(b), end(b), [&](int j) { 
    cartesian.push_back(i * i + j * j); 
  });
});

although I find this harder to read, and doesn't really help much since for_each is an algorithm that happens to have an in-built language construct, similar to transform.


From c++20, ranges and views have been added, and so you can get considerably more creative:

namespace rs = std::ranges;
namespace rv = std::views;

auto product = [=](int i) 
{
   auto  op = [=](int j) { return i * i + j * j;};

   return b | rv::transform(op);
};
  
rs::copy(a | rv::transform(product) 
           | rv::join,
         std::back_inserter(cartesian));

Again, the original nested for loops are probably the best approach for a cartesian product, but this should give you a taste for the exciting possibilities.

Here's a demo.

cigien
  • 57,834
  • 11
  • 73
  • 112
  • 3
    Simple is elegant – Kunal Mukherjee Oct 17 '20 at 18:12
  • Thank you for the comprehensive answer. Well, I'm actually in it for exploring and making use of the new features of c++, I mean the `c++11` and higher. meanwhile a small question: If I use the `vector cartesian(4);` line, it prints `0 0 0 0 10 17 13 20` i.e. it adds 4 unwanted zeros. If I delete the size argument `4` and just write `vector cartesian;` then it prints correctly the `10 17 13 20` What could be the reason for it? – Lars Malmsteen Oct 17 '20 at 19:54
  • That initialization puts 4 default-constructed ints into cartesian. Remove that. I've added a demo showing what to do anyway. I'm glad you're learning new techniques, too many people still want to write `for (int i = 0; i < ` :) – cigien Oct 17 '20 at 19:59