Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I factor out nested for loops?

Tags:

c++

Often times, I need to do bulk operations on a set of class data. Consider these classes:

#include <vector>


class Component {
public:
    bool isFixed;
    int a, b, c;

    Component():
    isFixed(false),
    a(0), b(0), c(0)
    {}
};


class System {
public:
    std::vector<Component> components;

    System(int numComponents):
    components(numComponents)
    {}
};


class Universe {
public:
    std::vector<System> systems;

    Universe(int numSystems, int numComponents):
    systems(numSystems, System(numComponents))
    {}
};

Now, doing a single operation to every Component in a Universe becomes a matter of looping through all the Components of all the Systems in a Universe: a nested for loop.

// Fixes a Component.
//
void fixComponent(Component& c) {c.isFixed = true;}


// Adds a number to the pieces of a Component.
//
void addToComponent(Component& cmp, double k)
{
    cmp.a += k;
    cmp.b += k;
    cmp.c += k;
}


// Fixes all components in a Universe.
//
void fixAllComponents(Universe& universe)
{
    for(std::size_t i = 0; i < universe.systems.size(); ++i) {

        System& thisSystem = universe.systems.at(i);

        for(std::size_t j = 0; j < thisSystem.components.size(); ++j) {
            fixComponent(thisSystem.components.at(j));
        }
    }
}


// Adds a number to all components in a Universe.
//
void addToAllComponents(Universe& universe, double k)
{
    for(std::size_t i = 0; i < universe.systems.size(); ++i) {

        System& thisSystem = universe.systems.at(i);

        for(std::size_t j = 0; j < thisSystem.components.size(); ++j) {
            addToComponent(thisSystem.components.at(j), k);
        }
    }
}

Writing the iterative for loops twice is OK, but I could easily have 20 different task to perform on this data, having to rewrite the double for every time. Needless to say, this can be error-prone. It would be nice if I could somehow reuse the iterating code and just focus on the distinct individual tasks.

Question Is there a standard way to "factor out" the for loops when iterating over a set?

Attempt After some thinking, I decided to write a function that takes 2 parameters, the object containing the data to iterate over and a pointer to a function that performs the task.

void forEachComponent(Universe& u, void (*task)(Component&))
{
    for(std::size_t i = 0; i < u.systems.size(); ++i) {

        System& thisSystem = u.systems.at(i);

        for(std::size_t j = 0; j < thisSystem.components.size(); ++j) {
            task(thisSystem.components.at(j));
        }
    }
}

Now, if I want to fix a component, I can simply call forEachComponent() and pass in the task to be performed.

Universe theUniverse(20, 30);
forEachComponent(theUniverse, fixComponent);

The obvious problem with this "solution" is that for every task that involves different parameters like addToComponent(), I have to write another function that takes a pointer to a function with those parameters, which defeats the purpose of factoring out the for loops.

like image 842
Mode77 Avatar asked Jan 01 '23 14:01

Mode77


2 Answers

You can reuse the logic for iterating over the components by using functors.

template <typename Functor >
void forEachComponent(Universe& universe, Functor functor)
{
    for(std::size_t i = 0; i < universe.systems.size(); ++i) {

        System& thisSystem = universe.systems.at(i);

        for(std::size_t j = 0; j < thisSystem.components.size(); ++j) {
            functor(thisSystem.components.at(j));
        }
    }
}

// Fixes all components in a Universe.
void fixAllComponents(Universe& universe)
{
   forEachComponent(universe, [](Component& c) {fixComponent(c);});
}


// Adds a number to all components in a Universe.
void addToAllComponents(Universe& universe, double k)
{
   forEachComponent(universe, [k](Component& c) {addToComponent(c, k);});
}

You can simplify forEachComponent by using range-for loops.

template <typename Functor >
void forEachComponent(Universe& universe, Functor functor)
{
    for(System& system : universe.systems) {
        for(Component& c : system.components) {
            functor(c);
        }
    }
}
like image 182
R Sahu Avatar answered Jan 03 '23 05:01

R Sahu


An OOP way to do this is define an abstract class:

class Task {
    void execute(Component &) = 0;
}

Now you can define forEachComponent() as

void forEachComponent(Universe& u, Task& task)

and call task.execute() in the for loop.

Alternatively, you can define forEachComponet() as a template:

template <class Task>
void forEachComponent(Universe& u, Task& task)

And now anything passed into this function must override operator().

like image 35
Code-Apprentice Avatar answered Jan 03 '23 05:01

Code-Apprentice