Question
· Jun 12, 2018

"IF THEN - $SELECT - $CASE" - What is the best way?

Hi all,

I'm wonder what could be the best way convert the sentence "Switch" C# into Cache code.

int caseSwitch = 1;

switch (caseSwitch)
{
    case 1:
    Console.WriteLine("Case 1");
    break;

    case 2:
    Console.WriteLine("Case 2");
    break;

    default:
    Console.WriteLine("Default case");
    break;

}

My first attempt was:

set caseSwith = 1

if (caseSwith = 1)
{
   w "Case 1"
}
else
{ 
  if (caseSwith = 2) 
  { 
    w "Case 2"
  }
  else
  {
      w "Default case"
  }
}  

Maybe, for complex conditions, it should be the best option, however I've used the $SELECT command

set caseSwith = 1

w $SELECT(caseSwith=1:"Case 1",caseSwith=2:"Case 2",1:"Default case")

It seems the best option for simple sentence (one line and clear)

Other options is using the $CASE command

set caseSwith = 1

w $CASE(caseSwith, 1:"Case 1",2:"Case 2",:"Default case")

so, it is easier because it is checking only one variable to evaluate the condition.

My question is, what should be the best way for complex conditions?

set val1 = 1
set val2 = 2
set val3 = 3

if ((val1>=3)&&(val1<=9))
{
   if (val2=0)
   {
       w "Condition 1"
   } 
   else
   {
     if  (val2>0)
     {
         w "Condition 2"
     }
   }
}
else
{
   if (val2=1)
   {
       if (val3<3)
       {
          w "Condition 3"
       } 
       else
       {
          w "Condition default 1"
       }
   }
   else
   {
      if ((val2>1) && (val2<9))
      {
         w "Condition 4"
      }
      else
      {
         w "Condition default 2"
      }
   }
}

As you see, it so complex because the condition is linked to 3 variables.

Trying to use $SELECT with this example

set val1 = 1
set val2 = 2
set val3 = 3

w $SELECT((val1>=3)&&(val1<=9): $SELECT(val2=0:"Condition 1",val2>0:"Condition 2"),val2=1:$SELECT(val3<3:"Condition 3",1:"Condition default 1"),val2'=1:$SELECT((val2>1) && (val2<9):"Condition 4",1:"Condition default 2"))

The line is longer and quite complicated to read

I haven't used $CASE because it affect to more than one variable.

So I don't want to create hundreds line of IF THEN conditions, but I want to have a clear code and easy for maintenance.

Please comment on how do you would solve this problem

Discussion (16)2
Log in or sign up to continue

but remember about performance ...

/// Process private global with indirection
ClassMethod Test(val = 3) [ ProcedureBlock = 0 ]{

  s:'$d(^||addr) ^||addr(1)=1, (^(2),^(8),^(9))=289, ^(3)=3
  @("t1case" _ $g( ^||addr( val ) ) ) Q  
  
t1case1  !, "Case 1" Q
t1case3  !, "Case 3" Q
t1case289  !, "Case 2 or 8 or 9" Q
t1case  !,"Case default" Q
}

/// Local variable with indirection
ClassMethod Test2(val = 3) [ ProcedureBlock = 0 ]{

  s:'$d(addr) addr(1)=1, (addr(2),addr(8),addr(9))=289, addr(3)=3
  @( "t2case"_$g( addr( val ) ) ) Q  
  
t2case1  !, "Case 1" Q
t2case3  !, "Case 3" Q
t2case289  !, "Case 2 or 8 or 9" Q
t2case  !,"Case default" Q
}

/// Without indirection
ClassMethod Test3(val = 3) [ ProcedureBlock = 0 ]{
  if val=1 !, "Case 1" Q
  if val=3 !, "Case 3" Q
  if (val=2)!(val=8)!(val=9) !, "Case 2 or 8 or 9" Q
  !,"Case default"
}

/// Process private global without indirection
ClassMethod Test4(val = 3) [ ProcedureBlock = 0 ]{

  s:'$d(^||addr) ^||addr(1)=1, (^(2),^(8),^(9))=289, ^(3)=3
  $case($g(^||addr(val)),1:t4case1,289:t4case289,3:t4case3,:t4case)
  Q  
  
t4case1  !, "Case 1" Q
t4case3  !, "Case 3" Q
t4case289  !, "Case 2 or 8 or 9" Q
t4case  !,"Case default" Q
}

/// Local variable without indirection
ClassMethod Test5(val = 3) [ ProcedureBlock = 0 ]{

  s:'$d(addr) addr(1)=1, (addr(2),addr(8),addr(9))=289, addr(3)=3
  $case($g(addr(val)),1:t5case1,289:t5case289,3:t5case3,:t5case)
  Q  
  
t5case1  !, "Case 1" Q
t5case3  !, "Case 3" Q
t5case289  !, "Case 2 or 8 or 9" Q
t5case  !,"Case default" Q
}

ClassMethod Run() {
repeat = 1000
ts = $zh for i=1:1:repeat ..Test( $r(10)+1 ) t1 = ( $zh - ts )
ts = $zh for i=1:1:repeat ..Test2( $r(10)+1 ) t2 = ( $zh - ts )
ts = $zh for i=1:1:repeat ..Test3( $r(10)+1 ) t3 = ( $zh - ts )
ts = $zh for i=1:1:repeat ..Test4( $r(10)+1 ) t4 = ( $zh - ts )
ts = $zh for i=1:1:repeat ..Test5( $r(10)+1 ) t5 = ( $zh - ts )
!, "do @( ^||addr() )        : ", t1
!, "do @( addr() )             : ", t2
!, "if val=case Q                : ", t3
!, "do $case( ^||addr() ) : ", t4
!, "do $case( addr() )      : ", t5
}

Output: 

do @( ^||addr() )     : .019535
do @( addr()    )     : .016482
if val=case Q         : .015183
do $case( ^||addr() ) : .015971
do $case( addr() )    : .015078

Suppose there is such a code:

int caseSwitch = 1;
 
switch (caseSwitch)
{
  case 1:
  Console.WriteLine("Case 1");
  //a lot of code
  break;
 
  case 2:
  case 8-9:
  Console.WriteLine("Case 2 or 8 or 9");
  //a lot of code
  break;
 
  case 3:
  Console.WriteLine("Case 3");
  //a lot of code
  break;
 
  /*...
  a lot conditions
  ...*/
 
  default:
  Console.WriteLine("Default case");
  //a lot of code
  break;
}

In this case

  1. $select;if/elseif/else is not effective because for last conditions will have to iterate through all the previous ones
  2. $case is redundant: you will have to specify the same code multiple times for different values

For this particular case, I suggest using a transition table, for example:

ClassMethod Test(caseSwitch 3) [ ProcedureBlock = 0 ]
{
  s:'$d(^||addr) ^||addr(1)=1,
                (^(2),^(8),^(9))=289,
                 ^(3)=3
  
  @("case"_$g(^||addr(caseSwitch),"def"))
  q  

case1
  "Case 1"
  q
case289
  "Case 2 or 8 or 9"
  q
case3
  "Case 3"
  q
casedef
  "Default case"
  q
}

Of course, you can modify the example to call procedures, functions, class methods, etc.

So, in order.

  1. There is no limit to perfection.

    Better then  d:(v=2)!(v=8)!(v=9) t3case289 q

  2. There are one error in the Test2 method:
      s:'$d(addraddr(1)=1, (addr(2),addr(8),addr(9))=289, add(3)=3
  3. There are error in the Test3 method:
      d:v=1 t3case1 Q ;will work in any way
      d:v=2 t3case289 Q  d:v=8 t3case289 Q  d:v=9 t3case289 Q 
      d:v=3 t3case3 Q 
      d t3case 
    
  4. Speed will depend on many factors: RAM size, number of conditions (the more of them, the slower the lasts of them will be executed and more RAM is required).

Can also speed up, abandoning the indirect:

  s:'$d(addr3addr3(1)=1, (addr3(2),addr3(8),addr3(9))=289, addr3(3)=3
  
  d $case(addr3(value),1:t3case1,289:t3case289,3:t3case3,:t3case)
  ##; w !,r
  q  

t3case1
  r="Case 1"
  q
t3case289
  r="Case 2,8-9" q
t3case3
  r="Case 3"
  q
t3case
  r"Case default"
  q

But the quickest option is if/elseif/else, since here is used inline-call, and not external-call.

Hi,

I won't claim this is an answer, because it's not quite the same and people may object to the structure, but here is one solution that is used quite a lot in code I look after. Basically, a subroutine is called and then tests are done and a Quit is used to drop out when a match is found. Often used for validation, something like this that returns a result in the zER variable:

V1 ; Validate ORGC
   S ORG=zORG WC2^hZUTV zER'="" Q
   I IPACC<9,'$D(^hIW(WAID)) zER="No details set up for ward" Q
   D WARDON^hILO1 LOCK zER="Ward in use" Q

VQ2 Q

Apologies for the old-fashioned code! However, you can see each test can be quite complex and using lots of variables, but it is easy to understand as long as you expect the structure to work that way.

This is very similar to the "clean code" solution of making the whole thing into a function that returns a value:

ClassMethod Main(val1 As %String, val2 As %String)
 {
  write ..MyOutput(val1,val2)
 }

ClassMethod MyOutput(val1 As %String, val2 As %String) As %String
 {
  if val1 = 1 return "case 1"
  if val1 = 2, val2="*" return "case 2"
  return "default match"
 }

Of course there are probably as many answers as there are Cache programmers!  :-)

You might want to restructure your multiple conditions per $case in a somewhat unusual fashion like this


Class User.FunWithCASE
{
ClassMethod CaseDemo(pNum as %Integer, pBoole as %Boolean = 0, pStr as %String) as %String
{
    return $case(1,  /* whatever is true first */
                 (pNum>100)   :"num gt than 100",
                 ((pBoole=1) && (pStr["Green Bay Packers")) : "True Superbowl Champion found!!!!",
                 ((pNum<=2) || ($length(pStr)>40))          : "num lt 2 or a looong string",
                                                            : ..NestedCase(pNum,pBoole,pStr))    
}

ClassMethod NestedCase(pNum as %Integer, pBoole as %Boolean = 0, pStr as %String) as %String
{
        return $case(1,  /* whatever is true first */
                 (pBoole=0)               : "not true ",
                 (pStr["Miami Dolphins")  : "Whatever",
                 ($e(pStr,*-4,*)="kees")  : "Mon or Yan found",
                                          : "Nothing rhymed in here")_" (nested)"
}

ClassMethod RunTest()
{
    for tNum=1,2,10,101
    {
        for tBoole = 0,1
        {
            set tList= $lb( "Green Bay Packers",
                            "MiamiDolphins",
                            "New York Yankees",
                            "Maxwell's Silver Hammer",
                             $tr($j(" ",41)," ","*")
                           )
            set tPos=0,tStr=""
            while ($listnext(tList,tPos,tStr))                 
            {
                write !,"CaseDemo("_tNum_","_$case(tBoole,1:"true",:"false")_","_$$$quote(tStr)_")"
                       ,?40,..CaseDemo(tNum,tBoole,tStr)
            } 
        }
    }
}
}

Obviously, a complex condition cannot produce simple code. I do this in that way:

 set val1 = 1
 set val2 = 2
 set val3 = 3

 w $SELECT(
     (val1>=3)&&(val1<=9):
     $SELECT(
        val2=0:"Condition 1",
        val2>0:"Condition 2"),
        val2=1:$SELECT(
                val3<3:"Condition 3",
                     1:"Condition default 1"
              ),
       val2'=1:$SELECT(
                 (val2>1) && (val2<9):"Condition 4",
                                    1:"Condition default 2"
               )
    ),!

It's more clear and readable.