3

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 *)

Bob Werner
  • 421
  • 1
  • 3
  • 7

4 Answers4

2

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}
*)
Michael E2
  • 235,386
  • 17
  • 334
  • 747
  • 1
    Another way to do the simpler example in the OP, bypassing the switch table: Scan[exec, Take[{"D", "C", "B", "A"}, -nn]] – Michael E2 Jan 19 '22 at 22:39
0

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} *)

Simon Rochester
  • 6,211
  • 1
  • 28
  • 40
0

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.

Bob Werner
  • 421
  • 1
  • 3
  • 7
0

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 &lt; 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.

Ben Izd
  • 9,229
  • 1
  • 14
  • 45