How can I optimize the multiple (matrix) switching / case algorithm?

Is it possible to optimize this (matrix) algorithm:

// | case 1 | case 2 | case 3 | // ------|--------|--------|--------| // | | | | // case a| a1 | a2 | a3 | // | | | | // case b| b1 | b2 | b3 | // | | | | // case c| c1 | c2 | c3 | // | | | | switch (var) { case 1: switch (subvar) { case a: process a1; case b: process b1; case c: process c1; } case 2: switch (subvar) { case a: process a2; case b: process b2; case c: process c2; } case 3: switch (subvar) { case a: process a3; case b: process b3; case c: process c3; } } 

The code is pretty simple, but you should imagine more complex with a more "switch / case".

I work with 3 variables. According to them, the values ​​1, 2, 3 or a, b, c or alpha, beta, charlie have different processes to achieve. Is it possible to optimize it in any other way than through the "switch / case" series?

(A question has already been asked in French here ).

Edit : (from Dran Dane's answers to the comments below. They may also be in this more prominent place!)
“optimize” should be understood in terms of having less code to write , less “switch / case”. The idea is to improve readability, maintainability , and not performance.

There may be a way to write less code using the Chain of Responsibility, but this solution is not optimal for all points because it requires the creation of many objects in memory.

+4
source share
11 answers

Developpez.com solution Yes, you can optimize it and make it much cleaner. You cannot use this Responsibility chain with Factory:

 public class ProcessFactory { private ArrayList<Process> processses = null; public ProcessFactory(){ super(); processses = new ArrayList<Process>(); processses.add(new ProcessC1()); processses.add(new ProcessC2()); processses.add(new ProcessC3()); processses.add(new ProcessC4()); processses.add(new ProcessC5(6)); processses.add(new ProcessC5(22)); } public Process getProcess(int var, int subvar){ for(Process process : processses){ if(process.canDo(var, subvar)){ return process; } } return null; } } 

Then, just as your processes implement the interface process using canXXX, you can easily use:

 new ProcessFactory().getProcess(var,subvar).launch(); 
0
source

It looks like you want a “state machine” , where with these cases you can activate different processes or “states”. In C, this is usually done using an array (matrix) of function pointers.

So you essentially make an array and put the right pointers on the right icons, then use your "var" as an index in the right "process", and then you call it. You can do this in most languages. Thus, various inputs into the machine activate various processes and transfer them to different states. This is very useful for numerous applications; I myself use it all the time in MCU development.

Edit: Valya pointed out that I should probably show the base model:

 stateMachine[var1][var2](); // calls the right 'process' for input var1, var2 
+13
source

There are no good answers to this question :-(

since most of the answer depends on

  • Effective goals (what is meant by "optimization", which is inconvenient for nested switches)
  • The context in which this construct will be applied (what are the final needs implicit for the application)

TokenMacGuy was wise to ask about goals. I took the time to check out the question and its answers on a French site, and I'm still puzzled as to goals ... The last answer by Dran Dane seems to indicate less code / better readability, but let the review be sure:

  • Processing speed: not a problem, nested switches are pretty effective, maybe less than 3 multiplications to get an index into the map table, but maybe not even.
  • Readability: Yes, perhaps a problem. As the number of variables and levels increases, the combinatorial explosion fires, and the switch statement format tends to propagate the branch spot and related values ​​along the length of the vertical extension. In this case, a 3-dimensional (or more) table is initialized with fct. pointers combine branch values ​​and a function that will be called on one line.
  • Writing less code: Sorry, don't help much here; at the end of the day we need to take into account a relatively large number of combinations, and the “card”, regardless of its shape, must be written somewhere. Code generators such as TokenMacGuy may come in handy, in which case it seems a little redundant. Generators have their place, but I'm not sure that it is. One of two cases: if the number of variables and the level is small enough, the generator is not worth it (it takes more time to configure it than to write the actual code in the first place), if the number of variables and levels is significant, the generated code is difficult to read, difficult to maintain. ..)

In short, my recommendation that the code is more readable (and slightly faster for writing) is the table / matrix approach described on the French site.

This solution consists of two parts:
one-time initialization of a 3-dimensional array (for 3 levels); (or, if preferred, a “more attractive” container structure), such as a tree). This is done using code like:

 // This is positively more compact / readable ... FctMap[1][4][0] = fctAlphaOne; FctMap[1][4][1] = fctAlphaOne; .. FctMap[3][0][0] = fctBravoCharlie4; FctMap[3][0][1] = NULL; // impossible case FctMap[3][0][2] = fctBravoCharlie4; // note how the same fct may serve in mult. places 

And a relatively simple snippet where functions should be called:

 if (FctMap[cond1][cond2][cond3]) { retVal = FctMap[cond1][cond2][cond3](Arg1, Arg2); if (retVal < 0) DoSomething(); // anyway we're leveraging the common api to these fct not the switch alternative .... } 

A case that may induce NOT to use the above solution if the combination space is relatively sparsely populated (many branches are not used in the switch tree) or if some functions require a different set of parameters ; For both of these cases, I would like to connect the solution proposed by Joel Goodwin first here , and which essentially combines different keys for several levels into one longer key (with a separator character if necessary), essentially smoothing the problem down to a long but single-level operator switch .

Now...

The real discussion should be about why we need such a mapping / decision tree in the first place . Unfortunately, this requires an understanding of the true nature of the underlying application. Of course, I am not saying that this indicates poor design. In some applications, a large dispatch section may make sense. However, even with the C language (which, according to French site participants, disqualified an object-oriented design), you can use an object-oriented methodology and templates. In any case, I diverge ...) It is possible that the application as a whole will be better served by alternative design patterns, where the "information tree on what to call when" was distributed in several modules and / or several objects.

Sorry to say this in rather abstract terms, this is simply the lack of application specifics ... The question remains: to challenge the idea that we need this large dispatch tree; think of alternative approaches to the application as a whole.

Alors, Bon's chance !; -)

+5
source

Depending on the language, some form of hash map with a pair (var, subvar) as a key and first-class functions as values ​​(or something like what your language offers in order to get closer to this, for example, class instances, extending some correct interface in Java) is likely to provide maximum performance - and the complete conciseness of obtaining the corresponding function (or something; -) from a key-based card and its execution leads to high readability for readers who are familiar with the language and such functional idioms .

+4
source

The idea of ​​a function pointer is probably best (according to mjv, Shhnap). But if the code under each case is quite small, it can be excessive and lead to more obfuscation than anticipated. In this case, I could implement something quick and fast reading as follows:

 string decision = var1.ToString() + var2.ToString() + var3.ToString(); switch(decision) { case "1aa": .... case "1ab": .... } 

Not familiar with your specific scenario, so perhaps the more appropriate previous sentences.

+1
source

Yes, there is definitely an easier way to do this: faster and easier . The idea is basically the same as suggested by Alex Martelli. Instead of viewing the problem as two-dimensional, see it as a lookup table of the same size.

This means combining var, subvar, subsubvar, etc. to get one unique key and use it as an entry point into the lookup table.

The way to do this depends on the language used. With python combining var, subvar, etc., To build a tuple and use it as a key in dictionnary is enough.

With C or in this way, it is usually easier to convert all keys to enumerations and then combine them using logical operators to get only one number that you can use on your switch (this is also an easy way to use the switch instead of comparing strings with a subjunctive mood) . You also get another advantage. As usual, several treatments in different branches of the original switch are the same. With the original form, it's pretty hard to make this obvious. You probably have some calls for the same functions, but at different points in the code. Now you can group identical cases when writing a switch.

I have used such a conversion several times in production code, and it is easy to do and maintain.

You may end up with something like this ... the mix function obviously depends on the specifics of your application.

 switch (mix(var, subvar)) { case a1: process a1; case b1: process b1; case c1: process c1; case a2: process a2; case b2: process b2; case c2: process c2; case a3: process a3; case b3: process b3; case c3: process c3; } 
+1
source

I had the same problem though for the immanent mess of a 5-parameter nested switch. I understood why to introduce all these cases O (N 5 ), why even invent "nested" function names, if the compiler can do this for me. And all this led to a "nested specialized template switcher" referring to a "specialized template database".

A bit hard to write. But I thought it was worth it: the result is a database of "knowledge" that is very easy to maintain, debug, add, etc. And I must admit: a sense of pride.

 // the return type: might be an object actually _doing_ something struct Result { const char* value; Result(): value(NULL){} Result( const char* p ):value(p){}; }; 

Some types of variables to switch:

  // types used: struct A { enum e { a1, a2, a3 }; }; struct B { enum e { b1, b2 }; }; struct C { enum e { c1, c2 }; }; 

The "primary declaration" of the knowledge base: the "api" of the nested switch.

  // template database declaration (and default value - omit if not needed) // specializations may execute code in stead of returning values... template< A::e, B::e, C::e > Result valuedb() { return "not defined"; }; 

Actual switching logic (compressed)

  // template layer 1: work away the first parameter, then the next, ... struct Switch { static Result value( A::ea, B::eb, C::ec ) { switch( a ) { case A::a1: return SwitchA<A::a1>::value( b, c ); case A::a2: return SwitchA<A::a2>::value( b, c ); case A::a3: return SwitchA<A::a3>::value( b, c ); default: return Result(); } } template< A::ea > struct SwitchA { static Result value( B::eb, C::ec ) { switch( b ) { case B::b1: return SwitchB<a, B::b1>::value( c ); case B::b2: return SwitchB<a, B::b2>::value( c ); default: return Result(); } } template< A::ea, B::eb > struct SwitchB { static Result value( C::ec ) { switch( c ) { case C::c1: return valuedb< a, b, C::c1 >(); case C::c2: return valuedb< a, b, C::c2 >(); default: return Result(); } }; }; }; }; 

And the knowledge base itself

  // the template database // template<> Result valuedb<A::a1, B::b1, C::c1 >() { return "a1b1c1"; } template<> Result valuedb<A::a1, B::b2, C::c2 >() { return "a1b2c2"; } 

Here's how to use it.

 int main() { // usage: Result r = Switch::value( A::a1, B::b2, C::c2 ); return 0; } 
+1
source

Perhaps you want to create code?

 #! /usr/bin/python first = [1, 2, 3] second = ['a', 'b', 'c'] def emit(first, second): result = "switch (var)\n{\n" for f in first: result += " case {0}:\n switch (subvar)\n {{\n".format(f) for s in second: result += " case {1}:\n process {1}{0};\n".format(f,s) result += " }\n" result += "}\n" return result print emit(first,second) #file("autogen.c","w").write(emit(first,second)) 

This, of course, is quite difficult to read, and you may actually need a more convenient template language for your dirty work, but it will facilitate some parts of your task.

0
source

If C ++ is an option, I would try to use a virtual function and possibly double dispatch. This can make it a lot cleaner. But this is likely to pay off only if you still have a lot to do.

This article article on DDJ.com can be a good entry.

0
source

If you are simply trying to eliminate the two-level switch / case statements (and preserve some vertical space), you can encode the two values ​​of the variables into one value and then include it:

 // Assumes var is in [1,3] and subvar in [1,3] // and that var and subvar can be cast to int values switch (10*var + subvar) { case 10+1: process a1; case 10+2: process b1; case 10+3: process c1; // case 20+1: process a2; case 20+2: process b2; case 20+3: process c2; // case 30+1: process a3; case 30+2: process b3; case 30+3: process c3; // default: process error; } 
0
source

If your C # language and your variations are short enough and do not contain special characters, you can use reflection and do this with just a few lines of code. Thus, instead of manually creating and maintaining an array of function pointers, use the one provided by the infrastructure!

Like this:

 using System.Reflection; ... void DispatchCall(string var, string subvar) { string functionName="Func_"+var+"_"+subvar; MethodInfo m=this.GetType().GetMethod(fName); if (m == null) throw new ArgumentException("Invalid function name "+ functionName); m.Invoke(this, new object[] { /* put parameters here if needed */ }); } void Func_1_a() { //executed when var=1 and subvar=a } void Func_2_charlie() { //executed when var=2 and subvar=charlie } 
0
source

All Articles