Question

Essentially I've got a bunch of formulas in two giant methods in a class designed to do math transformations and evaluations to multiple inputs. Where the inputs are actually lists of inputs (as there are some sums involved too). Later on I want to optimize this code by utilizing GPU/CPU accelerated matrix multiplications and additions but for now I'm using the basic for-loops.

Lets say hypothetically i'd like to grow to several dozen cases and right now i have less than 10..

Something like:

enum EnumType {
    SUPER_FUNCTION,
    MEGA_FUNCTION,
    ..
}

float doMathStuff(EnumType functionType, List<float> a, List<float> b...) {
    switch(functionType) {
        case SUPER_FUNCTION:
                    if(situationA) {
                        switch(something else) {

                        }
                    } else {
                        switch(something else) {

                        }
                    }
                return stuff;
        case MEGA_FUNCTION:
                for(..) {
                    if(situationA) {
                        switch(something else) {

                        }
                    } else {
                        switch(something else) {

                        }
                    }
                }
                return stuff;
        ...
    }
}

My problem is that to support the functions I'm ending up with SEVERAL hundred lines of code in each of my switch statements which is making it rather cumbersome to go through. I shudder to think about maintaining this once I add more cases.

Any ideas as to how to keep this nightmare-in-the-making in check?

BTW: This is my own personal project and I have total freedom to do any changes.

Was it helpful?

Solution

I had similar design goals on a polynomial calculation library I've written. Wanted to have different kinds of operations, some of which have special optimization possibilities.

Long story short: Object-orientation helps a lot, if it is done right.

Since you didn't provide an actual domain, let's use my example of polynomials. Since it's about polynomials I created the Polynomial of course:

public interface Polynomial {
   Polynomial add(Polynomial other) { ... }

   Polynomial dot(Polynomial other) { ... }

   ...etc...
}

However, there are special cases, for example if the polynomial is modulo something, and you add a bunch of them, you can potentially delay the modulo operation (which is pretty expensive), and/or export the calculation to GPU.

Since adding a bunch of Polynomials is some higher operation, I've added it to the PolynomialRing where these things come from. So that has:

public class PolynomialRing {
   ...
   public Polynomial add(Polynomial[] ps) { ... }
   ...
}

Initially this was a for loop using Polynomial.add(), just like in your example, later I optimized it to delay the modulo and use a construct that the JVM translates to SIMD instructions, which is pretty neat.

So, you'll have to model your math domain properly. There is no generic mechanism for everything. For every thing that you want to have or want to optimize you'll have to find a place.

OTHER TIPS

At a minimum, you should create functions to encapsulate parts of the behavior (and organize the code)

float doMathStuff(EnumType functionType, List<float> a, List<float> b...) {
  switch(functionType) {
    case SUPER_FUNCTION: return handleSuperFunction(args); break;       
    case MEGA_FUNCTION: return handleMegaFunction(args); break;
  }
}

float handleSuperFunction(someArgs) {
  if(situationA) {
    return handleSuperSituationA(args);
  }
  else {
    return handleSuperSituationB(args);
  }
}

float handleSuperSituationA(args) {
  switch(something else) {
    case foo: return blah;
    ...
  }
}

Once you do this, it should be easier for you and others to read and follow. Now, consider if these are functions of "objects" and could be made polymorphic. Without knowing your domain this is difficult for me to say.

Added

One big advantage is that you can name the various subcases. For example, might be handleImaginaryRoot() and handleRealRoot() or some such. (My analytical math is getting a little stale, YMMV...)

Licensed under: CC-BY-SA with attribution
scroll top