10
$\begingroup$

Mathematica has the While function which is very convenient when we need to process some code until the attainment of a certain state (identified with a condition). Personally, I also like very much the Loop-Until approach.

An advantage of the Loop body Until condition is that the body being performed first, it is not necessary to set initial values to the variables being tested in condition. In While, all the variables must be set so that the preliminary test can return a value.

I have this definition below but would like to know if there are more efficient ways to define a Loop.

Loop[body_, test_] := Block[{}, body; While[! test, body]]
SetAttributes[Loop, HoldAll]

Edit

With the nice comments of everyone, I updated the code so that a MaxIterations option can be given, and usage and error messages are provided:

Unprotect[Loop];
Loop::usage = "Loop[body, test] performs body then verify test until test returns True. Loop[body, test, MaxIterations-> i] sets a maximal number of iterations (default infinity).";
Loop::maxiter = "Maxiterations `1` reached. Aborting...";
Loop[body_, test_, opt___] := Block[
  {res, currenti = 1, maxi = MaxIterations /. {opt} /. Options[Loop]},
  body;
  While[(!test) && (currenti<maxi), 
    res = body; 
    If[maxi == ++currenti, Message[Loop::maxiter, currenti]; Abort[]]
  ];
  res
  ]
SetAttributes[Loop, HoldAll]
Options[Loop] = {MaxIterations -> Infinity};
Protect[Loop];

Here are two examples

n = 1;
Loop[Print[n]; ++n, n > 4]
n = 1;
Loop[Print[n]; ++n, n > 4, MaxIterations -> 2]
$\endgroup$
6
  • $\begingroup$ I updated the answer. $\endgroup$
    – Szabolcs
    Commented Jan 2, 2022 at 11:18
  • 2
    $\begingroup$ I'll just add the obligatory reference: Alternatives to procedural loops and iterating over lists in Mathematica. $\endgroup$
    – MarcoB
    Commented Jan 2, 2022 at 15:02
  • $\begingroup$ Another option is to set a boolean flag (OMG!) before you enter the loop, and then turn the flag off inside the loop when condition is met. As in keepRunning = True; While[keepRunning, ...... If[condition,keepRunning=False]]. Notice there is no no need to do Break[] now. This will run one time at least. This is how we used to program in the good old days :) $\endgroup$
    – Nasser
    Commented Jan 2, 2022 at 17:40
  • 1
    $\begingroup$ @Nasser: yes, indeed, the Loop structure was very popular with languages such as Pascal. I also find it more intuitive than the While. Your version is neat as well. What about performance in terms of computation times? $\endgroup$ Commented Jan 2, 2022 at 19:10
  • 1
    $\begingroup$ There is Until in the Wolfram Function Repository $\endgroup$ Commented Jan 3, 2022 at 13:46

3 Answers 3

9
$\begingroup$

I tend to use

While[True,
  body;
  If[condition, Break[]]
]

but what you show is equally good. This is just an alternative.

Try to avoid procedural loops in Mathematica when you can.

This is of course not always possible, but we can define a functional equivalent of the concept. In IGraph/M, I have a utility function called IGTryUntil. IGTryUntil[cond][x] will keep evaluating x until cond[x] becomes True, then it returns x. One of the main use cases is rejection sampling: generate random objects until they satisfy a condition. For example, a random connected graph: IGTryUntil[ConnectedGraphQ][RandomGraph[{20,20}]].

If you are going to create a custom function for this, it does not matter that much if the implementation is not simple. It's the usage of the function that should be simple. You can see the implementation of IGTryUntil here:

https://github.com/szhorvat/IGraphM/blob/a65dceb5ab1135e77b496a05888aa94787d3be91/IGraphM/Utilities.m#L513

$\endgroup$
2
  • 2
    $\begingroup$ "Try to avoid procedural loops..." Good point, we might use Loop[body_, test_] := Block[{res}, body; While[! test, res = body]; res] $\endgroup$ Commented Jan 2, 2022 at 12:27
  • $\begingroup$ Both answers were equally usefull. I had to make a choice... $\endgroup$ Commented Jan 4, 2022 at 16:29
10
$\begingroup$

Here's a simpler version:

SetAttributes[Until,HoldAll]
Until[body_, test_] := While[body; !test]
$\endgroup$
2
  • $\begingroup$ There should be a negation to test but yes, very elegant! $\endgroup$ Commented Jan 2, 2022 at 18:06
  • 1
    $\begingroup$ @DenisCousineau Thanks, I fixed the bug. However, did you want to use Until[body, test] or Until[test, body]? $\endgroup$
    – Carl Woll
    Commented Jan 2, 2022 at 18:14
1
$\begingroup$

As mentioned by Denis Cousineau in his comment, Until is now part of the language (since V 13.1)

n = 1; list = {}; 
Until[n > 4, AppendTo[list, n++]
list

{1, 2, 3, 4}

This does the same as Carl Woll's answer with switched arguments:

SetAttributes[until, HoldAll]
until[test_, body_] := While[body; ! test]

n = 1; list = {}; 
until[n > 4, AppendTo[list, n++]]
list

{1, 2, 3, 4}

$\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.