3
$\begingroup$

Is there a nice way in Mathematica to express the equivalent of Switch where statements (not cases) fall through? Here is a toy example in C:

switch (n)
{
  case 4: printf ("4");
  /* fall through */
  case 3: printf ("3");
  /* fall through */
  case 2: printf ("2");
  /* fall through */
  case 1: printf ("1");
  break;
}

(In reality the statements would be computations, not just printf's.)

I've thought of two ways. One is to use Goto[] and Label[]:

ClearAll[trial1];

trial1[n_] := Module[
  {one, two, three, four},
  Goto[{one, two, three, four}[[n]]];
  
  Label[four];
  Print[4];
  
  Label[three];
  Print[3];
  
  Label[two];
  Print[2];
  
  Label[one];
  Print[1];
]

The other holds each statement in a list, takes the desired elements, and releases the holds:

ClearAll[trial2];

trial2[n_] := Module[
  {lst},
  lst = {
    Hold[Print[4];],
    Hold[Print[3];],
    Hold[Print[2];],
    Hold[Print[1];]
    };
  ReleaseHold[Take[lst, -n]];
]

Both work, but neither seems "clean" or Mathematica-like.

My question differs from this one where several cases fall through to the same statement.

Added later: Here is a sample. Calling the function with 3 results in the last three statements executing.

trial2[3]

(* 3 2 1 *)

$\endgroup$
2
  • $\begingroup$ Doesn't Which[ ] do what you are asking? $\endgroup$
    – bill s
    Jan 19, 2022 at 22:00
  • 1
    $\begingroup$ @BillS Documentation says that Which[] returns the value of the first test that evaluates True, not every subsequent value too. $\endgroup$
    – Bob Werner
    Jan 19, 2022 at 22:05

4 Answers 4

2
$\begingroup$

I recall once someone defending Mathematica's Switch[] not falling through, perhaps as less bug-prone as C's switch(). I can't recall what they recommended to do instead.

Here's a way to present the code blocks, even give them helpful names (other than "A", "B" etc. that I use below), and execute them conditionally in whatever order. While the C switch() is a perhaps a glorified assembly branch, this is basically an implementation of a C switch table (AFAIR) with a slight -- perhaps only very slight -- Mathematica flair. It also allows more general execution patterns than "fall-through," which was allowed in C because it was easy to implement in assembly language.

I took the OP's question to be general, not the specific, overly symmetric pattern in the example. That is, my solution may be applied to more complicated switch cases than a sequence of positive integers less than some value for n.

exec = <|   (* code texts *)
   "A" :> Print["Code 1"],
   "B" :> Print["Code 2"],
   "C" :> Print["Code 3"],
   "D" :> Print["Code 4"]|>;
switch = {  (* switch table with fall-through defined by lists *)
   4 -> {"D", "C", "B", "A"}, (* keys to code texts to be executed *) 
   3 -> {"C", "B", "A"},
   2 -> {"B", "A"},
   1 -> {"A"},
   _ :> Throw[$Failed]}; (* optional `default` *)
nn = 4;
Scan[exec, Replace[nn, switch], 1]
(*
  Code 4
  Code 3
  Code 2
  Code 1
*)

I think Scan and Replace are expressive of the intention. Replace will fail if nn does not have an appropriate value, even if there is no default case -- that is, it will leave nn unchanged if there is no default. ReplaceAll may be used instead -- the operator /. is convenient -- but it might replace parts of nn if it has integer parts. If bad input is a possibility, then Replace might be safer.

nn = 3;
Scan[exec, nn /. switch, 1]
(*
  Code 3
  Code 2
  Code 1
*)

One reason for using a list instead of an association for the switch is that the keys can be patterns. This also shows that the order of the code texts can be arbitrary.

switch2 = {
   4 | 3 -> {"D", "C", "B"}, (* pattern key *)
   2 -> {"B", "C"},  (* reversed order - can't be done in C *)
   1 -> {"A"}
   _ :> Throw[$Failed]};     (* optional `default` *)
nn = 3;
Scan[exec, Replace[nn, switch2], 1]
(*
  Code 4
  Code 3
  Code 2
*)

Here's the shortest way to type a workaround:

nn = 2;
nn /. switch2 /. exec
(*
  Code 2
  Code 3
  Out[]= {Null, Null}
*)
$\endgroup$
1
  • 1
    $\begingroup$ Another way to do the simpler example in the OP, bypassing the switch table: Scan[exec, Take[{"D", "C", "B", "A"}, -nn]] $\endgroup$
    – Michael E2
    Jan 19, 2022 at 22:39
0
$\begingroup$

I think your trial2 gets pretty close. This seems more idiomatic:

SetAttributes[trial3, HoldRest];
trial3[n_, list_] := Take[Unevaluated@list, -n]

trial3[3, {Echo[4], Echo[3], Echo[2], Echo[1]}]

(* >> 3 *)
(* >> 2 *)
(* >> 1 *)
(* {3,2,1} *)
$\endgroup$
0
$\begingroup$

An answer subsequently deleted by @Nassar started me thinking about a function whose argument indicates which individual statement. That function can be invoked multiple times by Scan[]ing a list:

ClearAll[trial3];

trial3[n_] := Scan[
  Switch[#,
    1, Print["1"],
    2, Print["2"],
    3, Print["3"],
    4, Print["4"]
    ] &,
   Take[{4, 3, 2, 1}, -n]
  ]

I'm interested in other solutions.

$\endgroup$
0
$\begingroup$

We could implement that style of Switch with Label and Goto:

ClearAll[cSwitch];
SetAttributes[cSwitch, HoldAllComplete];

cSwitch::OddCases = 
  "cSwitch called with `1` arguments. cSwitch must be called with an odd number of arguments.";

cSwitch[value_, pairs__] := 
 Module[{cases = HoldComplete[{pairs}], casesLength, begin, n = 1, matchFlag = False, lastValue},
  
  casesLength = cases /. HoldComplete[a_] :> Length[Unevaluated[a]];
  
  Block[{Break = Return},
   If[OddQ[casesLength], Message[cSwitch::OddCases, casesLength + 1]; 
    Return[Defer[cSwitch[value, pairs]]],

    Label[begin];

    If[matchFlag || MatchQ[value, Extract[cases, {1, n}, ReleaseHold]], 
     matchFlag = True; lastValue = Extract[cases, {1, n + 1}, ReleaseHold]];

    If[n < casesLength - 1, n += 2; Goto[begin], Return[lastValue]];
    ]
   ]]

Use it as just like Switch. It will execute all the cells after it finds a successful match until Break[] was called or reach the end, like C/Java. Also, if you use Break[1], 1 will be returned (ignore the red color).

Example

Consider this function:

ClearAll[dummyFn];
dummyFn[n_] := 
  cSwitch[n, 1, Print[1]; 1,
             2, Print[2], 
             3, Print[3]; Break[],
             4, Print[4]; 4,
             _, Print[Infinity]; -1
];

Test:

dummyFn[1]

(* Print 1 *)
(* Print 2 *)
(* Print 3 *)

(* Return Null *)
dummyFn[4]

(* Print 4 *)
(* Print Infinity *)

(* Return -1 *)
dummyFn[5]

(* Print Infinity *)

(* Return -1 *)

Thanks to @jojo for his answer in this post.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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