2

I have the following code in come C++ I need to translate to C#

int** _cartesianProduct(int sizeA, int sizeB) {

    int** array = (int**)malloc(sizeof(int*) * sizeA * sizeB + sizeof(int) * 2 * sizeA * sizeB);

    int* ptr = (int*)(array + sizeA * sizeB);
    for (int i = 0; i < sizeA * sizeB; ++i)
        array[i] = (ptr + 2 * i);

    for (int i = 0; i < sizeA; ++i)
        for (int k = 0; k < sizeB; ++k) {
            array[i * sizeB + k][0] = i;
            array[i * sizeB + k][1] = k;
        }

    return array;
}

This creates an array of "Cartesian products" for all possible combinations of the numbers passed to it. My specific confusion here is what this block is doing?

int* ptr = (int*)(array + sizeA * sizeB);
    for (int i = 0; i < sizeA * sizeB; ++i)
        array[i] = (ptr + 2 * i);

Or even more specifically, this line int* ptr = (int*)(array + sizeA * sizeB);?

MoonKnight
  • 23,214
  • 40
  • 145
  • 277
  • 1
    I don't quite understand the use of the pointer `ptr` to initialize `array`. Also, the fact that array is 2D, and that `array[i]` is being used in the first loop has got me confused here... – MoonKnight Apr 28 '20 at 16:00
  • 4
    That whole block appears to be effectively creating a jagged array. – ProgrammingLlama Apr 28 '20 at 16:01

2 Answers2

4

This allocates a single block of memory to store a 2D array, as an array of arrays. C arrays of arrays are stored as an array of pointers to further arrays; my first thought was 'yuck', but there's some elegance here in that everything is returned as a single block of memory that can be free()ed in one go afterwards.

This code

  1. allocates space for (sizeA * sizeB) int-pointers, and then 2 * sizeA * sizeB; store this as an int** pointer, i.e. we'll use the first block of this as the top level of our 2D array
  2. (the code you quoted) sets up the pointers for the top level array to point to two-int blocks of the remaining memory
  3. uses the 2D array to store pairs of values in the range 0-sizeA, 0-sizeB

How to port this to C#? It depends on how you want to consume the values generated. I'd probably make this an array of value tuples,

var array = Enumerable.Range(0, sizeA).SelectMany(a =>
                Enumerable.Range(0, sizeB).Select(b => (a,b))).ToList();

or .ToArray(). If you do want the jagged array version you can new[] { a, b } instead in the inner select.

Rup
  • 33,765
  • 9
  • 83
  • 112
  • 1
    Ah, I see. Thanks for this. How would you suggest replicating this in C# - specifically the logic outlined in point 2? Clearly I want to avoid `unsafe` and the use of pointers, but it is not clear how this can be replicated cleanly without the use of pointers... – MoonKnight Apr 28 '20 at 16:04
  • 1
    Sorry, was just typing that! Do you need an identical single block of memory, for binary interoperability with existing code across IPC say? If not, and I'd guess not, I'd use tuples or normal jagged array. – Rup Apr 28 '20 at 16:06
  • One last thing, in the consuming code, the returned `int** cartiesianProduct` is used in the following for loop `for(int i = 0; i < rowsA * rowsB; ++i) { int aIndex = cartesianProduct[i][0]; int bIndex = cartesianProduct[i][1]; }`. I would like to be able to access the elements via indexing can you advise? – MoonKnight Apr 28 '20 at 16:30
  • I've changed the second level array to a tuple with members a and b, so `int aIndex = cartesianProduct[i].a; int bIndex = cartesianProduct[i].b;`. Or using a foreach over the collection: `foreach(var index in cartesianProduct) { int aIndex = index.a, bIndex = index.b; ... } ` – Rup Apr 28 '20 at 16:34
  • What you have currently returns `List>`. – MoonKnight Apr 28 '20 at 16:40
  • Aarrggh, so it does. The first Select should be a SelectMany. – Rup Apr 28 '20 at 16:41
2

A bit of topic.

This code may look like a C++ code, but I would call it like that. It is more like C code which pretends to be C++, since result of malloc is explicitly cast to int** (needed in C++ in C obsolete).

C++ version of it can look like this:

std::vector<std::array<int, 2>> cartesianProduct(int sizeA, int sizeB) {
    std::vector<std::array<int, 2>> result(sizeA * sizeB);

    for (int i = 0; i < sizeA; ++i) {
        for (int k = 0; k < sizeB; ++k) {
            result[i * sizeB + k] = { i, k };
        }
    }
    return result;
}
Marek R
  • 32,568
  • 6
  • 55
  • 140