0

Suppose I have the following code currently:

double P[2][2][10]; 
std::vector<double> b, r, n; 

//
// Assume that 10 doubles are pushed to each vector and
// that P has all its allocated values set.
//    

for(int t=0; t<10; ++t) {
    P[0][0][t] = b[t]*r[t]+n[t];
    P[0][1][t] = b[t]*2.0*r[t]+(1.0-n[t]);
    P[1][0][t] = b[t]*b[t]+r[t]*n[t];
    P[1][1][t] = r[t]+n[t];
}

This is a trivial example to illustrate my question. In real cases, P will often be P[9][9][100] and the equations will be a little more messy. My question is, basically, how can I use macros to make these equations more readable?

In particular, here is a non-working code fragment to illustrate how I would like the solution to this question to look:

#define P(i,j) P[i][j][t]
#define b b[t]
#define r r[t]
#define n n[t]

for(int t=0; t<10; ++t) {
    P(0,0) = b*r+n;
    P(0,1) = b*2.0*r+(1.0-n);
    P(1,0) = b*b+r*n;
    P(1,1) = r+n;
}

There is at least one problem with this code fragment. For example, it will expand the "r" and "n" in the For-loop statement according to the macro definition. But you get the point.

The goal here is to develop a method for entering in equations that can be more easily read and checked for errors. I'm open to non-macro solutions, though it seems to me that macros could be helpful here.


With respect to the non-working code fragment I posted above to illustrate how I imagine a solution might look... is it possible to use macros in such a way that macro substitution occurs only inside the For-loop body? or at least not until after the For-loop statement?

synaptik
  • 8,971
  • 16
  • 71
  • 98
  • Don't use macros overload `operators` create operators for vector addition and multiplication. – andre Aug 13 '13 at 16:01
  • 3
    Don't do this [mandatory macro rant: it's evil, unmanagable, hard to read etc], you can probably get away with a function taking P and the 4 values? Btw if you ask yourself coding questions, don't start with 'how do I use X to solve Y' but rather 'how do I solve Y' without on beforehand putting restrictions on the type of solution. Especially not if X == macros :] – stijn Aug 13 '13 at 16:03
  • 2
    "how can I use macros to make these equations more readable?" You can't. They are perfectly readable as-is. Perhaps add a little more whitespace. – John Dibling Aug 13 '13 at 16:07
  • 1
    Orthogonal to your question but if you make your `P` array `double P[10][2][2];` to that `t` is the first index you'll get a lot better cache locality. – Mark B Aug 13 '13 at 16:09
  • @MarkB Good point. Thanks. In the real code, I'm using `double P[10*2*2]` and then translating i,j,k into a single index value. – synaptik Aug 13 '13 at 16:12
  • @stijn I take your point. That's why I said I'm open to non-macro solutions. – synaptik Aug 13 '13 at 16:13

5 Answers5

2

A posible solution is defining all the macros just before the for and then #undef all macros after the for. Example:

#define P(i,j) P[i][j][t]
#define b b[t]
#define r r[t]
#define n n[t]

for(int t=0; t<10; ++t) {
    P(0,0) = b*r+n;
    P(0,1) = b*2.0*r+(1.0-n);
    P(1,0) = b*b+r*n;
    P(1,1) = r+n;
}

#undef P
#undef b
#undef r
#undef n

In my opinion, macros are not the best solution for this problem, but I cannot find any other solution.

xorguy
  • 2,594
  • 1
  • 16
  • 14
  • Ahh, neat. I didn't realize you can define/undefine at different locations in the code. Could you put the `#define` statements inside the For-loop body before the equations? (This way, the "n" and "r" in the For-loop statement wouldn't be expanded.) – synaptik Aug 13 '13 at 16:07
  • #defines and #undefs can be put anywhere in the code, so that would be possible. – xorguy Aug 13 '13 at 16:09
1

What's wrong with good old-fashioned loop-local variables here? I'm going to assume that you call your vectors something more meaningful than b so I'll give them slightly longer names. I also took the liberty of adding some eye-clarity-granting whitespace in your very dense equations:

double P_arr[10][2][2]; 
std::vector<double> b_arr, r_arr, n_arr; 

//
// Assume that 10 doubles are pushed to each vector and
// that P has all its allocated values set.
//    

for(int t = 0; t < 10; ++t)
{
    const double b = b_arr[t];
    const double r = r_arr[t];
    const double n = n_arr[t];
    double** P = P_arr[t];

    P[0][0] = b * r + n;
    P[0][1] = b * 2.0 * r + (1.0 - n);
    P[1][0] = b * b + r * n;
    P[1][1] = r + n;
}
Mark B
  • 95,107
  • 10
  • 109
  • 188
1

It's usually a good idea to separate conceptually different things. This usually goes a long way to improve code clarity, maintainability, and flexibility.

There are at least two different things here:

  1. You loop through arrays of data.

  2. You compute something.

The best thing you could do is to separate these things into different functions, or better yet, classes. Something like this would do:

class MyFavoriteMatrix
{
  private:
    double m_P[2][2];
  public:
    MyFavoriteMatrix( double b, double r, double n ) {
      m_P[0][0] = b*r+n;
      m_P[0][1] = b*2.0*r+(1.0-n);
      m_P[1][0] = b*b+r*n;
      m_P[1][1] = r+n;
    }
    double Get( int i, int j ) {
      return m_P[i][j];
    }
}

Then your loop would look like this:

for(int t = 0; t < 10; ++t)
{
  // Computation is performed in the constructor
  MyFavoriteMatrix mfm( b[t], r[t], n[t] );

  // Now put the result where it belongs
  P[0][0][t] = mfm.Get( 0, 0 );
  P[0][1][t] = mfm.Get( 0, 1 );
  P[1][0][t] = mfm.Get( 1, 0 );
  P[1][1][t] = mfm.Get( 1, 1 );
}

Notice these things about such solution:

  1. If you change your mind about the storage containers (for example, as Mark B suggested, change P[2][2][10] to P[10][2][2] for performance reasons), you computational code won't be affected at all, only the relatively simple loop will change a bit.

  2. If you need to perform the same computation in 10 different places you won't have to copy the computational code: you'll just call MyFavoriteMatrix there.

  3. You you find out that the computational code needs to change you only need to modify it in one place: in MyFavoriteMatrix constructor.

  4. Each piece of code looks neat, thus less chances for a typo.

And all that you get by separating conceptually different thing - computation and iteration.

Michael
  • 5,775
  • 2
  • 34
  • 53
  • And if you get rif of P[][][] using MyFavoriteMatrix you have a design –  Aug 13 '13 at 17:05
1

Surprised nobody's suggested using references. http://en.wikipedia.org/wiki/Reference_(C%2B%2B)

typedef double Array22[2][2]; // for convenience...

for(int t = 0; t < 10; ++t)
{
    const double &b(b_arr[t]);
    const double &r(r_arr[t]);
    const double &n(n_arr[t]);
    Array22 &P(P_arr[t]);

    P[0][0] = b * r + n;
    P[0][1] = b * 2.0 * r + (1.0 - n);
    P[1][0] = b * b + r * n;
    P[1][1] = r + n;
}
Roddy
  • 66,617
  • 42
  • 165
  • 277
0

This is what operators are design to help with. Here is an example with add and multiply operator. You will still need a add other as needed.

#include <iostream>
#include <algorithm>
#include <vector>
#include <functional>

std::vector<int>& operator+(std::vector<int>& a, std::vector<int>& b) {
    std::transform (a.begin(), a.end(), b.begin(), a.begin(), std::plus<int>());
    return a;
}

std::vector<int>& operator*(std::vector<int>& a, std::vector<int>& b) {
    std::transform (a.begin(), a.end(), b.begin(), a.begin(), std::multiplies<int>());
    return a;
}

int main() {
    int a[6] = {1,2,3,4,5,6};
    int b[6] = {6,7,8,9,10,11};
    std::vector<int> foo(a, a+6);
    std::vector<int> bar(b, b+6);

    foo = foo + bar;
    std::cout << foo[0] <<std::endl;
}
andre
  • 7,018
  • 4
  • 43
  • 75