Written by

Project Manager & Head of Interoperability at Salutic Soluciones, S.L.
Question Kurro Lopez · 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

Comments

John Murray · Jun 12, 2018

If you use the IF style, I suggest you also consider using the ELSEIF clause. See doc here.

 set caseSwitch = 1

 if (caseSwitch = 1)
 {
   write "Case 1"
 }
 elseif (caseSwitch = 2)
 { 
   write "Case 2"
 }
 else
 {
    write "Default case"
 }
0
Kurro Lopez  Jun 12, 2018 to John Murray

Then, according your opinion, for complex conditions the best way is use IF ELSEIF instead of $SELECT or other similar
That's right

0
Kurro Lopez  Jun 18, 2018 to Vitaliy Serdtsev

I like it. I think this is the cleanest way to solve my problem.

Thanks a lot

0
Oleg Dmitrovich  Jul 20, 2018 to Vitaliy Serdtsev

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

0
Vitaliy Serdtsev · Jun 14, 2018

Suppose there is such a code:

int caseSwitch =1;
 
switch(caseSwitch){case1:
Console.WriteLine("Case 1");//a lot of codebreak;
 
case2:case8-9:
Console.WriteLine("Case 2 or 8 or 9");//a lot of codebreak;
 
case3:
Console.WriteLine("Case 3");//a lot of codebreak;
 
/...
a lot conditions
.../
 
default:
Console.WriteLine("Default case");//a lot of codebreak;}
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:

<FONT COLOR="#000080">ClassMethod </FONT><FONT COLOR="#000000">Test(</FONT><FONT COLOR="#ff00ff">caseSwitch </FONT><FONT COLOR="#000000">= </FONT><FONT COLOR="#000080">3</FONT><FONT COLOR="#000000">) [ </FONT><FONT COLOR="#000080">ProcedureBlock </FONT><FONT COLOR="#000000">= 0 ]
{
  </FONT><FONT COLOR="#0000ff">s</FONT><FONT COLOR="#000000">:'</FONT><FONT COLOR="#0000ff">$d</FONT><FONT COLOR="#000000">(^||addr) ^||addr(1)=1,
                (^(2),^(8),^(9))=289,
                 ^(3)=3
  
  </FONT><FONT COLOR="#0000ff">d </FONT><FONT COLOR="#000000">@(</FONT><FONT COLOR="#008000">"case"</FONT><FONT COLOR="#000000">_</FONT><FONT COLOR="#0000ff">$g</FONT><FONT COLOR="#000000">(^||addr(</FONT><FONT COLOR="#800000">caseSwitch</FONT><FONT COLOR="#000000">),</FONT><FONT COLOR="#008000">"def"</FONT><FONT COLOR="#000000">))
  </FONT><FONT COLOR="#0000ff">q  

</FONT><FONT COLOR="#ff0000">case1   </FONT><FONT COLOR="#0000ff">w </FONT><FONT COLOR="#008000">"Case 1"   </FONT><FONT COLOR="#0000ff">q </FONT><FONT COLOR="#ff0000">case289   </FONT><FONT COLOR="#0000ff">w </FONT><FONT COLOR="#008000">"Case 2 or 8 or 9"   </FONT><FONT COLOR="#0000ff">q </FONT><FONT COLOR="#ff0000">case3   </FONT><FONT COLOR="#0000ff">w </FONT><FONT COLOR="#008000">"Case 3"   </FONT><FONT COLOR="#0000ff">q </FONT><FONT COLOR="#ff0000">casedef   </FONT><FONT COLOR="#0000ff">w </FONT><FONT COLOR="#008000">"Default case"   </FONT><FONT COLOR="#0000ff">q </FONT><FONT COLOR="#000000">}</FONT>

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

0
Kurro Lopez  Jul 19, 2018 to Oleg Dmitrovich

Good point !!!!

0
Oleg Dmitrovich  Jul 19, 2018 to Vitaliy Serdtsev

Hi, Vitaliy!

1,2,3 - fix it and update, thank you;  4 - agree with you

0
Vitaliy Serdtsev  Jul 20, 2018 to Oleg Dmitrovich

There are one error in the Test5 method: t4case* -> t5case*

0
Vitaliy Serdtsev  Jul 19, 2018 to Oleg Dmitrovich

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).
0
Vitaliy Serdtsev  Jul 20, 2018 to Oleg Dmitrovich

Can also speed up, abandoning the indirect:

  <FONT COLOR="#0000ff">s</FONT><FONT COLOR="#000000">:'</FONT><FONT COLOR="#0000ff">$d</FONT><FONT COLOR="#000000">(</FONT><FONT COLOR="#800000">addr3</FONT><FONT COLOR="#000000">) </FONT><FONT COLOR="#800000">addr3</FONT><FONT COLOR="#000000">(1)=1, (</FONT><FONT COLOR="#800000">addr3</FONT><FONT COLOR="#000000">(2),</FONT><FONT COLOR="#800000">addr3</FONT><FONT COLOR="#000000">(8),</FONT><FONT COLOR="#800000">addr3</FONT><FONT COLOR="#000000">(9))=289, </FONT><FONT COLOR="#800000">addr3</FONT><FONT COLOR="#000000">(3)=3
  
  </FONT><FONT COLOR="#0000ff">d $case</FONT><FONT COLOR="#000000">(</FONT><FONT COLOR="#800000">addr3</FONT><FONT COLOR="#000000">(</FONT><FONT COLOR="#800000">value</FONT><FONT COLOR="#000000">),1:</FONT><FONT COLOR="#ff0000">t3case1</FONT><FONT COLOR="#000000">,289:</FONT><FONT COLOR="#ff0000">t3case289</FONT><FONT COLOR="#000000">,3:</FONT><FONT COLOR="#ff0000">t3case3</FONT><FONT COLOR="#000000">,:</FONT><FONT COLOR="#ff0000">t3case</FONT><FONT COLOR="#000000">)
  </FONT><FONT COLOR="#008000">##; w !,r
  </FONT><FONT COLOR="#0000ff">q  

</FONT><FONT COLOR="#ff0000">t3case1   </FONT><FONT COLOR="#0000ff">s </FONT><FONT COLOR="#800000">r</FONT><FONT COLOR="#000000">=</FONT><FONT COLOR="#008000">"Case 1"   </FONT><FONT COLOR="#0000ff">q </FONT><FONT COLOR="#ff0000">t3case289   </FONT><FONT COLOR="#0000ff">s </FONT><FONT COLOR="#800000">r</FONT><FONT COLOR="#000000">=</FONT><FONT COLOR="#008000">"Case 2,8-9" </FONT><FONT COLOR="#0000ff">q </FONT><FONT COLOR="#ff0000">t3case3   </FONT><FONT COLOR="#0000ff">s </FONT><FONT COLOR="#800000">r</FONT><FONT COLOR="#000000">=</FONT><FONT COLOR="#008000">"Case 3"   </FONT><FONT COLOR="#0000ff">q </FONT><FONT COLOR="#ff0000">t3case   </FONT><FONT COLOR="#0000ff">s </FONT><FONT COLOR="#800000">r</FONT><FONT COLOR="#000000">= </FONT><FONT COLOR="#008000">"Case default"   </FONT><FONT COLOR="#0000ff">q</FONT>

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

0
Mike.W · Jun 15, 2018

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!  :-)

0
Gerd Nachtsheim · Jul 3, 2018

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)
            } 
        }
    }
}
}
0
Oleksandr Baranyuk · Jun 2, 2020

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.

0