2

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.

3
  • Does this answer your question? Approaches to checking multiple conditions?
    – gnat
    Apr 5 '20 at 20:23
  • 2
    If you cannot recollect and recite all potential values of an enum within a few seconds after the moment you define it (provided that you have properly thought about it up front), chances are you are messing up (you are using the wrong kind of abstraction). Use an interface and classes instead. enum constructs do not really serve "future extensibility" that well. Apr 7 '20 at 1:40
  • While I'm not sure I agree with your reasoning, I do agree that an interface and classes is ultimately more maintainable.
    – Akumaburn
    Apr 8 '20 at 20:53
2

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.

1
  • Decided to go with this type of design. I don't like cluttering with too many classes but the alternative is a giant class which i like even less.
    – Akumaburn
    Apr 8 '20 at 20:52
4

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...)

1
  • 1
    While this makes things a little easier to read, it doesn't get around the problem of how bloated the class itself is becoming, for that reason I picked the other solution.
    – Akumaburn
    Apr 8 '20 at 23:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.