Question

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?

Était-ce utile?

La solution

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.

Autres conseils

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;
}

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.

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;
}

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;
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top