Case Statements: flow value set into another?

Mike_HWGMike_HWG Member Posts: 104
Please bear with my lead-up before asking a question.

Background

In NAV, we have:
CASE <expression> OF
    <value set 1> :
        <statement 1>;
    <value set 2> :
        <statement 2>;
    <value set n> :
        <statement n>;
    [ELSE <statement n+1>]
END;

In C++
switch( <expression> )
{
    case <value set 1> :
        <statement 1>;
        break;
    case <value set 2> :
        <statement 2>;
        break;
    case <value set n> :
        <statement n>;
        break;
    [default: <statement n+1>;]
}



Question

Is there an equivalent NAV expression to match the C++ code below? I'm trying to say "If my case statement matches value set 1, start doing something. If my case statement matches value set 2 or 3, do both their stuff. If my case statement only matches value set 3, only do that stuff"
switch( <expression> )
{
    case <value set 1> :
        <statement 1>;
        break;                        //The code will hit the break and leave this structure
     case <value set 2> :
        <statement 2>;
                                        //Notice I didn't say break!  Value set 2 will flow on to value set 3!
     case <value set 3> :
         <statement 3>;
         break;
}
Michael Hollinger
Systems Analyst
NAV 2009 R2 (6.00.34463)

Answers

  • vaprogvaprog Member Posts: 1,139
    There's no exact replacement. One suggestion provided that <expression> has no side effects was
    CASE <expression> OF
        <value set 1> :
            <statement 1>;
        <value set 2>,  <value set n> :
            BEGIN
               IF <expression> IN [<value set 2>] THEN
                 <statement 2>;
              <statement n>;
            END;
        [ELSE <statement n+1>]
    END;
    
    Another suggestion was to use a sequence of IF statements instead taking into account when <statement> alters the result of <expression>.
  • David_SingletonDavid_Singleton Member Posts: 5,479
    Mike_HWG wrote:
    Is there an equivalent NAV expression to match the C++ code below? I'm trying to say "If my case statement matches value set 1, start doing something. If my case statement matches value set 2 or 3, do both their stuff. If my case statement only matches value set 3, only do that stuff"
    switch( <expression> )
    {
        case <value set 1> :
            <statement 1>;
            break;                        //The code will hit the break and leave this structure
         case <value set 2> :
            <statement 2>;
                                            //Notice I didn't say break!  Value set 2 will flow on to value set 3!
         case <value set 3> :
             <statement 3>;
             break;
    }
    

    Sure, try:
    CASE TRUE of
      value in [1] :
        <statement 1>;
      value in [3] :
        <statement 3>;
      value in [2,3] :
        <statement 2>;
        <statement 3>;
    END;
    

    Using the "CASE TRUE of" and "IN []" you can generally achieve what ever you want.
    David Singleton
  • DenSterDenSter Member Posts: 8,305
    Not sure about that C++ command, but in a C/AL CASE statement it only executes the one leg of the control value. Say you have a control variable MyInt with value 1, it will only execute the code in 1.
    MyInt := 1;
    CASE MyInt OF
      1: MyInt := 2;
      2: MyInt := 3;
    ELSE
    END;
    
    After this CASE statement is done, MyInt will be 2, because when the CASE was evaluated it only executed the leg for that value. Setting the control value to 2 as part of the code in 1 will not make it execute that code.

    By the way, you can evaluate multiple values in a CASE statement like this:
    CASE MyInt OF
      1: MESSAGE('it is one!');
      2,3: MESSAGE('it is two or three');
    ELSE
      MESSAGE('not one, not two, not three!');
    END;
    
  • vaprogvaprog Member Posts: 1,139
    Sure, try:
    CASE TRUE of
      value in [1] :
        <statement 1>;
      value in [3] :
        <statement 3>;
      value in [2,3] :
        <statement 2>;
        <statement 3>;
    END;
    

    Using the "CASE TRUE of" and "IN []" you can generally achieve what ever you want.
    David's code is equivalent to
    CASE value of
      1 :
        <statement 1>;
      3 :
        <statement 3>;
      2,3 :
        <statement 2>;
        <statement 3>;
    END;
    
    The 2nd 3 in both, David's code and my replacement given here is superfluous in that it newer will have any effect on what is executed. C/AL only executes the statements in the first matching branch, then jumps to the END.
    I suppose the repeating of <statment 3> is objectionable.

    David's suggestion can easily be turned into
    IF value in [1] THEN
        <statement 1>;
    ELSE IF value in [3] THEN
        <statement 3>;
    ELSE IF value in [2,3] THEN
      BEGIN
        <statement 2>;
        <statement 3>;
      END;
    
    which does exactly the same as the CASE statement. (Note that I used the IF within the ELSE as though we had an IF THEN ELIF ELSE structure when it comes to indenting (and pacing BEGIN...END)). With a structured sequence of IF statements, you have more liberty, though, to approximate whatever you want
    IF value IN [<value set 1>] THEN
      <statement 1>;
    ELSE BEGIN
      IF value IN [<value set 2>] THEN
        <statement 2>;
      IF value IN [<value set 2>,<value set 3>] THEN
        <statement 3>;
    END;
    
  • DenSterDenSter Member Posts: 8,305
    vaprog wrote:
    which does exactly the same as the CASE statement
    No it does not. The outcome might be the same, but technically it goes about evaluating the control variable differently. In a CASE statement, it only evaluates the one single leg that evaluates to TRUE. With a nested IF statement, it could potentially evaluate multiple expressions.

    To evaluate multiple values (meaning more than 2) for one control variable, a CASE statement is more efficient than a construction of nested IF statements, not to mention easier to read.
  • Mike_HWGMike_HWG Member Posts: 104
    DenSter wrote:
    vaprog wrote:
    which does exactly the same as the CASE statement
    No it does not. The outcome might be the same, but technically it goes about evaluating the control variable differently. In a CASE statement, it only evaluates the one single leg that evaluates to TRUE. With a nested IF statement, it could potentially evaluate multiple expressions.

    I think you may have misread, and vaprog lists what I believe makes sense: A CASE statement is essentially an IF - ELSE IF - ELSE IF(n). In both situations, CAL is going to find the first argument that evaluates true within the soonest sequential leg, execute it, and leave as soon as possible, WITHOUT touching any other leg.

    If we look at David's code and pass in a value of 3, then the last leg will never be evaluated.
    CASE TRUE of
      value in [1] :
        <statement 1>;
      value in [3] :
        <statement 3>;
      value in [2,3] :
         //If we pass in 3, we never get here!
        <statement 2>;
        <statement 3>;
    END;
    


    Therefore, if I ever want to mirror the functionality of my C++ snippet, I would want to use nested if/else statements. I could technically use the below code, but you can quickly see how nasty it becomes!
    case <expression> OF
        <value set 1>:
            <statement 1>;
        ELSE BEGIN
            IF <value set 2> THEN
                <statement 2>;
            IF <value> IN [<value set 2>, <value set 3>] THEN
                <statement 3>;
        END;
    END;
    

    Therefore, I'm thinking vaprog's last code snippet is the implementation that we have to live with!
    Michael Hollinger
    Systems Analyst
    NAV 2009 R2 (6.00.34463)
  • DenSterDenSter Member Posts: 8,305
    Mike_HWG wrote:
    I think you may have misread
    No I don't think I misread. A CASE statement is NOT the same as a nested IF/ELSEIF.

    What makes no sense to me is that you would have two different blocks of code for the same value of the control variable, so I always assumed that having two blocks for value '3' was a typo.

    My solution would be to have a block for values '2,3' in a CASE statement, and then have an IF statement inside there to catch value 2, which in that case seems to be the exception.
    CASE MyInt OF
      1: <Statement 1>;
      2,3: 
        BEGIN
          IF MyInt = 2 THEN BEGIN
            <Statement 2>;
          END;
          <Statement 3>;
        END;
    ELSE
      <Statement x>;
    END;
    
    More than one way to skin a cat, whatever works for you :mrgreen:
  • DenSterDenSter Member Posts: 8,305
    See for yourself. Save the code below in a new text file and import it into NAV. It's a form with a CASE and an ELSEIF structure. Set the value to 3 and debug both buttons. The CASE statement jumps right into value = 3, where the IF/ELSEIF evaluates each ELSE until the expression evaluates to TRUE. They are simply not the same.
    OBJECT Form 50000 CASE ELSEIF
    {
      OBJECT-PROPERTIES
      {
        Date=11/14/11;
        Time=[ 1:37:00 PM];
        Version List=;
      }
      PROPERTIES
      {
        Width=2640;
        Height=2420;
      }
      CONTROLS
      {
        { 1240060000;CommandButton;220;770;2200;550 ;CaptionML=ENU=CASE;
                                                     OnPush=BEGIN
                                                              CASE MyInt OF
                                                                1: MESSAGE('the value is one');
                                                                2: MESSAGE('the value is two');
                                                                3: MESSAGE('the value is three');
                                                              ELSE
                                                                MESSAGE('the value is four or more');
                                                              END;
                                                            END;
                                                             }
        { 1240060001;CommandButton;220;1540;2200;550;CaptionML=ENU=IF-ELSEIF;
                                                     OnPush=BEGIN
                                                              IF MyInt = 1 THEN
                                                                MESSAGE('the value is one')
                                                              ELSE IF MyInt = 2 THEN
                                                                MESSAGE('the value is two')
                                                              ELSE IF MyInt = 3 THEN
                                                                MESSAGE('the value is three')
                                                              ELSE
                                                                MESSAGE('the value is four or more');
                                                            END;
                                                             }
        { 1240060002;TextBox;220  ;220  ;1700 ;440  ;SourceExpr=MyInt }
      }
      CODE
      {
        VAR
          MyInt@1240060000 : Integer;
    
        BEGIN
        END.
      }
    }
    
    
  • Mike_HWGMike_HWG Member Posts: 104
    edited 2011-11-14
    DenSter wrote:
    See for yourself. Save the code below in a new text file and import it into NAV. It's a form with a CASE and an ELSEIF structure. Set the value to 3 and debug both buttons. The CASE statement jumps right into value = 3, where the IF/ELSEIF evaluates each ELSE until the expression evaluates to TRUE. They are simply not the same.

    I'd be vary careful in comparing what the debugger does to what the compiler actually creates. I admit, I don't know the answer, but I haven't been very pleased in the past with how the debugger sometimes bounces around, especially after being used to Visual Studio.

    That said, I see where you were going.
    My solution would be to have a block for values '2,3' in a CASE statement, and then have an IF statement inside there to catch value 2, which in that case seems to be the exception.

    Hmm, not bad :-k I really like that.

    However, when it came down to the actual implementation, I chose option X! :lol: Here's what I've been working on. I chose this way because
    -setting the Customer Group is better represented in a separate 'thought'.
    The IF statement's evaluation speed is minimal. therefore, I'm not taking a critical hit every time I evaluate.
    IF CustomerGroupFilter <> CustomerGroupFilter::None THEN
      SETRANGE("Customer Group",CustomerGroupFilter)
    ELSE
      SETRANGE("Customer Group");
    
    CASE CustomerGroupFilter OF
      CustomerGroupFilter::Customer:
        BEGIN
          CurrForm.DistrictBillToNoFilterCtrl.ENABLED(FALSE);
          DistrictBillToNoFilter := '';
        END;
      CustomerGroupFilter::District:
        BEGIN
          IF DistrictBillToNoFilter <> '' THEN
            SETFILTER("District Bill-to No.",DistrictBillToNoFilter)
          ELSE BEGIN
            CurrForm.CustGroupCodeFilterCtrl.ENABLED(FALSE);
            DistrictBillToNoFilter := '';
            CustGroupCodeFilter := '';
          END;
        END;
      CustomerGroupFilter::"All Customers", CustomerGroupFilter::None:
        BEGIN
          CurrForm.DistrictBillToNoFilterCtrl.ENABLED(FALSE);
          CurrForm.CustGroupCodeFilterCtrl.ENABLED(FALSE);
          DistrictBillToNoFilter := '';
          CustGroupCodeFilter := '';
        END;
    END;
    

    Thanks everyone for the discussions, I haven't broken down code to the basic building blocks like this for quite some time. It's very nice to step back, think it over, and 'tighten the mental belt!'
    Michael Hollinger
    Systems Analyst
    NAV 2009 R2 (6.00.34463)
  • matttraxmatttrax Member Posts: 2,309
    I think that's just a case of the NAV Debugger simplifying things. It looks like it jumps straight to the correct one, but there's no way it knows that it is the correct one unless the previous ones have been evaluated. It's not the first time I've seen the debugger mislead someone.

    Here is some code proving they are actually evaluated. You'll get four messages, three from the CheckCondition function and one from the OnRun trigger.

    OnRun()
    CASE TRUE OF
    CheckCondition(1, 3): MESSAGE('the value is one');
    CheckCondition(2, 3): MESSAGE('the value is two');
    CheckCondition(3, 3): MESSAGE('the value is three');
    ELSE
    MESSAGE('the value is four or more');
    END;

    CheckCondition(MyInt : Integer;Number : Integer) Result : Boolean
    Result := (MyInt = Number);
    MESSAGE('Condition: ' + FORMAT(Result));
  • DenSterDenSter Member Posts: 8,305
    Well if you force it into a function with a return value of course it has to go through, but that doesn't mean that it doesn't jump right into the right value when they are literal.

    The point was that a CASE statement is evaluated differently than a nested IF construction. My contention is that when evaluating multiple values for a single control variable, a CASE statement is more efficient and easier to read.
  • matttraxmatttrax Member Posts: 2,309
    DenSter wrote:
    My contention is that when evaluating multiple values for a single control variable, a CASE statement is more efficient and easier to read.

    Agree.
    DenSter wrote:
    but that doesn't mean that it doesn't jump right into the right value when they are literal.
    I was going to say I totally disagree, but as it turns out:

    "While semantically these two code segments may be the same, their implementation is usually different. Whereas the IF..THEN..ELSEIF chain does a comparison for each conditional statement in the sequence, the SWITCH statement normally uses an indirect jump to transfer control to any one of several statements with a single computation."

    It's always a good day when you learn something new. :D
  • Mike_HWGMike_HWG Member Posts: 104
    matttrax wrote:
    the SWITCH statement normally uses an indirect jump to transfer control to any one of several statements with a single computation."

    Wait a second on that... so how does it pick? Does it choose sequentially like I always thought, or pick based on quickest solution, or...? Can you then VERIFY that it will ALWAYS choose that jump?

    I present the code below, which will print 'three or two'... basic, but you can see where I'm going with this. Can I ALWAYS rely on NAV to pick the first in the sequence that matches?
    //Pass in a value of 2 and see what happens!
    
    CASE i OF
        1:
          MESSAGE('One');
        3,2:
          MESSAGE('Three or two');
        2:
          MESSAGE('Two');
    END;
    
    Michael Hollinger
    Systems Analyst
    NAV 2009 R2 (6.00.34463)
  • DenSterDenSter Member Posts: 8,305
    It's irrelevant what it does because you should not have code like that. Why would you EVER have more than one for any given value? As far as I'm concerned that's a programming error, and if you'd work for me I would have you fix it :mrgreen:

    If you need it to do something extra for the value 2, you catch it in the '2,3' block like this:
    //Pass in a value of 2 and see what happens!
    
    CASE i OF
        1:
          MESSAGE('One');
        3,2:
          BEGIN
            MESSAGE('Three or two');
            IF i = 2 THEN
              MESSAGE('Two');
          END;
    END;
    
  • Mike_HWGMike_HWG Member Posts: 104
    DenSter wrote:
    It's irrelevant what it does because you should not have code like that. Why would you EVER have more than one for any given value? As far as I'm concerned that's a programming error, and if you'd work for me I would have you fix it :mrgreen:

    Of course you wouldn't ever want to code that way, but saying it is irrelevant isn't a choice - you may one day have to clean it for someone else who is dead and gone (hello government coding job? :lol: ).
    Unpredictable code branches seem like a recipe for spaghetti at lower levels, and there was a lazy person working on the parser logic :wink: . Either we haven't found the answer or it's undocumented.

    I know I'm being anal, but now I'm just really interested. :D

    Anyways, mattrax said:
    mattrax wrote:
    "While semantically these two code segments may be the same, their implementation is usually different. Whereas the IF..THEN..ELSEIF chain does a comparison for each conditional statement in the sequence, the SWITCH statement normally uses an indirect jump to transfer control to any one of several statements with a single computation."

    Though I find the following passage in the Microsoft Navision Development I - C/SIDE Introduction (8359B) Chapter 14, page 272 :-s
    When the CASE statement is executed, the expression is evaluated first. This
    expression is sometimes called the selector. Then, in turn, each of the values in
    each value set is evaluated.

    If one of the values in the first value set matches the value of the expression, then
    the first statement is executed. If one of the values in the second value set
    matches the value of the expression, then the second statement is executed. This
    continues until one of the statements is executed.
    If the last value set has been reached and no statement has been executed, then
    the ELSE clause is checked. If not, the second value set is checked. If there is an
    ELSE clause, then its statement is executed. If there is no ELSE clause, then no
    statement is executed for this CASE statement.
    Note that only one of the statements is executed. If there is more than one value
    set that contains the same value, only the first one of them is executed.

    EDIT: Note that I won't be surprised if the MS documentation is wrong. It hasn't been the first time!
    Michael Hollinger
    Systems Analyst
    NAV 2009 R2 (6.00.34463)
  • matttraxmatttrax Member Posts: 2,309
    Note my quote was not from any NAV text. It was from a book on assembly language as I was curious how it usually works once everything is broken down that far. It's just talking about what normally happens, not what necessarily happens in NAV.

    Anyway, why does this matter again?
  • DenSterDenSter Member Posts: 8,305
    Mike_HWG wrote:
    Either we haven't found the answer or it's undocumented.
    Don't know about you but I had the answer four days ago. Remove the ambiguity from the code, fix it and move on.
  • matttraxmatttrax Member Posts: 2,309
    :cry: I'm such a geek. Why can't I leave well enough alone? :cry: Here is some code I threw together to measure the execution time of evaluating which case is true one million times, then doing it 100 times and taking the average.
    Number := 1; //or 5 or 10
    FOR j := 1 TO 100 DO BEGIN
      StartTime := TIME;
      FOR i := 1 TO 1000000 DO BEGIN
        CASE Number OF
           1: BEGIN END;
           2: BEGIN END;
           3: BEGIN END;
           4: BEGIN END;
           5: BEGIN END;
           6: BEGIN END;
           7: BEGIN END;
           8: BEGIN END;
           9: BEGIN END;
          10: BEGIN END;
        END;
      END;
      EndTime := TIME;
      TotalTime += EndTime - StartTime;
    END;
    
    Number := 1;
    FOR j := 1 TO 100 DO BEGIN
      StartTime := TIME;
      FOR i := 1 TO 1000000 DO BEGIN
        IF Number = 1 THEN BEGIN
        END ELSE IF Number = 2 THEN BEGIN
        END ELSE IF Number = 3 THEN BEGIN
        END ELSE IF Number = 4 THEN BEGIN
        END ELSE IF Number = 5 THEN BEGIN
        END ELSE IF Number = 6 THEN BEGIN
        END ELSE IF Number = 7 THEN BEGIN
        END ELSE IF Number = 8 THEN BEGIN
        END ELSE IF Number = 9 THEN BEGIN
        END ELSE IF Number = 10 THEN BEGIN
        END;
      END;
      EndTime := TIME;
      TotalTime += EndTime - StartTime;
    END;
    

    The average time for CASE statements for the nth case:
    1) 139.53
    5) 498.73
    10) 723.09

    The average time for IF statements for the nth if:
    1) 207.77
    5) 915.67
    10) 1745.51

    So whatever is going on behind the scenes, CASE statements are faster. Regardless, correct programming will solve any issues you might have. That's going to be it for me on this as well.
  • DenSterDenSter Member Posts: 8,305
    matttrax wrote:
    :cry: I'm such a geek
    Yes you are :mrgreen: =D>
  • David_SingletonDavid_Singleton Member Posts: 5,479
    I gave up when I realized that developers really don't care about total cost of ownership of software, just go fro the geeky-est solution that wastes the most time.
    David Singleton
  • DenSterDenSter Member Posts: 8,305
    What does TCO have to do with a question about syntax?
  • David_SingletonDavid_Singleton Member Posts: 5,479
    DenSter wrote:
    What does TCO have to do with a question about syntax?

    You said it in your earlier post. The benefit of CASE is readability, making code that is readable reduces the TCO of the system. It may take a few minutes more to write the code, and it may use more text, but in the long run it is worth it.
    David Singleton
  • DenSterDenSter Member Posts: 8,305
    Ok well that's taking it a bit off topic, but I wouldn't necessarily bring a management concept such as TCO into a syntax discussion, and I disagree with your generalization that "developers don't care". I think you'll find that the best developers care a great deal. Matt writing that code actually proves that he cares so much about it, that he writes some code to actually measure the performance. In doing so, he reveals his true geekiness :mrgreen:, but for sure rooted in a desire to be as efficient as possible.

    Surely not a reason to "give up"?
  • David_SingletonDavid_Singleton Member Posts: 5,479
    Daniel I have no idea what you read into my post. Some how you twisted my words to make it look like I was disagreeing with Matt which is 100% the opposite of my intention. But as I said once I have given up on this thread so that's all for me.
    David Singleton
  • DenSterDenSter Member Posts: 8,305
    Daniel I have no idea what you read into my post
    OK we figured that one out offline, you were replying to someone else's post :)
  • Mike_HWGMike_HWG Member Posts: 104
    Sorry all if I drug this further than it needed to go. When the things I used to take for granted no longer work here, I try to make sure I'm clear on the difference. That was done and over by about the 4th or 5th post

    I was just disturbed when we were getting beyond the base question (and far off topic) and it sounded like we weren't sure what the compiler was doing, so when everyone starts saying, "stop asking questions, just believe", I can't let go :-# .


    The party's over, the lights have been turned off, but I still have to dance out the last song. :oops: I've since done some separate research and discovered that denster/mattrax's initial post are probably both right:

    Compilers handle SWITCH/CASE statements differently, and without actually seeing the result, we can't be sure. However, the standard methodology is thus: If the case values are basic and within a narrow range, the compiler will generate a branch table where it can directly access an indexed branch. If the case values are complex or the resulting branch table would result in a large index/large memory footprint, the compiler will generate a result closer to a sequential IF/ELSEIF.


    And yes, this is my last post on this topic. :-# :-# :-#
    Michael Hollinger
    Systems Analyst
    NAV 2009 R2 (6.00.34463)
Sign In or Register to comment.