Home > database >  Combining a set of expressions
Combining a set of expressions

Time:10-16

I am trying to perform a sequence of operations on a 2D array (where dimensions are equal on both sides), where the sequence of operations is random. For each operation, it is a function which represents a transformation of a point to another point, which is represented as an expression. Is it possible to combine different expressions together without knowing their run time values?

For instance, to transform a point 1000 times, 1000 function calls would need to be done. However, since the same transformation would be applied to all points, is it possible to generalize these 1000 calls into a single call for the sake of this transformation?

The following is a simplified example of what I am trying to achieve.

int MoveRightIndex(int offset, int index)
{
    return index   offset;
}

int MoveLeftIndex(int offset, int index)
{
    return index - offset;
}

int MultIndex(int mult, int index)
{
    return index*mult;
}

int main()
{
    int index = 0;
    index = MoveRightIndex(5, index); // index   5
    index = MultIndex(3, index);      // index * 3
    index = MoveLeftIndex(2, index);  // index - 2
    printf("index is: %i\n", index);
    // Is there a way to generalize this "net" operation for the next point without calling the same functions again?
    //e.g. I know now for the next index the transformation is (index   5)*3 -2 -> 3*index   13
    //How can I apply 3*index   13 to index2 without doing the above 3 function calls?
}

CodePudding user response:

You could write a function to do the composition at compile time:

static inline int composite(int roffset, int mult, int loffset, int index)
{
    return MoveLeftIndex(loffset, MultIndex(mult, MoveRightIndex(roffset, index)));
}

and, the compiler may be able to do an astonishingly good job of optimizing that. You'd invoke:

index = composite(5, 3, 2, index);

to get the job done 'all at once'. This gives you no runtime flexibility, though.

An alternative is to define a structure defining operations and an array of the structure that can be initialized (with care) at runtime, along the lines of:

typedef int (*Transformer)(int value, int index);
typedef struct
{
    const char *t_name;
    Transformer t_func;
    int         t_value;
} TransformStep;

static TransformStep steps[] =
{
    { "MoveRightIndex", NULL, 5 },
    { "MultIndex",      NULL, 3 },
    { "MoveLeftIndex",  NULL, 2 },
};
enum { NUM_STEPS = sizeof(steps) / sizeof(steps[0]) };

typedef struct
{
    const char  *t_name;
    Transformer  t_func;
} NameToPointerMapping;

/* If the list of mappings gets big, sort the array and use search */
static const NameToPointerMapping mappings[] =
{
    { "MoveLeftIndex",    MoveLeftIndex   },
    { "MoveRightIndex",   MoveRightIndex  },
    { "MultIndex",        MultIndex       },
};
enum { NUM_MAPPINGS = sizeof(mappings) / sizeof(mappings[0]) };

static int MapFunctionNamesToPointers(size_t nsteps, TransformSteps *steps)
{
    if (nsteps == 0 || steps == NULL)
        return -1;
    for (size_t i = 0; i < nsteps; i  )
    {
         if (steps[i].t_func == NULL)
         {
             for (size_t j = 0; j < NUM_MAPPINGS; j  )
             {
                 if (strcmp(steps[i].t_name, mappings[j].t_name) == 0)
                 {
                     steps[i].t_func = mappings[j].t_func;
                     break;
                 }
             }
             if (steps[i].t_func == NULL)
             {
                 …report error - named transformer not found…
                 return -1;
             }
        }
    }
    return 0;
}

static int ApplyTransform(size_t nsteps, TransformStep *steps, int *pindex)
{
    if (nsteps == 0 || steps == NULL)
        return -1;  // Report error 
    if (steps[0].t_func == NULL && MapFunctionNamesToPointers(nsteps, steps) != 0)
        return -1;  // Report error
    int index = *pindex;
    for (size_t i = 0; i < nsteps; i  )
        index = (*steps[i].t_func(steps[i].t_value, index);
    *pindex = index;
    return 0;       // Report success
}

No compiler has been consulted about the accuracy of the conceptual code above.

There's nothing to stop you from creating the array dynamically with the function names and the values read from a file or standard input, and then mapping the names accordingly. The first time an array is used, the function names are mapped to the corresponding function pointers. Thereafter, the code simply cycles through the series of function calls.

There are endless optimizations and refactorings that could be applied, but the basic idea is that function names are mapped to corresponding function pointers, and then the array of transformation steps can be applied to the data. One change would be to do the mapping outside the ApplyTransform() function; another would be to factor the linear search into another small, separate (inline?) function and use that. The list continues. With care, you could build the list of functions and their pointers dynamically so that you can load shared libraries containing new transformations.

This design forces you to have one 'value' per function call. There are ways to allow multiple values instead, but that's rapidly getting more complex.

  •  Tags:  
  • c
  • Related