37
$\begingroup$

Mathematica has had NestWhile and NestWhileList for some time. But, to date, it has not implemented a built-in FoldWhile or a FoldWhileList. So, since these constructs seem useful to me, I have tried to brew my own. Here are my current implementations. Anyone have suggestions on how either of these might be improved. I'd be particularly interested in a variant of FoldWhile that did not require as much memory as FoldWhileList.

 FoldWhileList[f_, init_, list_, test_, m_, max_] := 
 Block[{i = 0}, 
    NestWhileList[(i = i + 1; f[#, Part[list, i]]) &, init, test, m, max]]

and

 FoldWhile[f_, init_, list_, test_, m_, max_] := 
    Last[FoldWhileList[f, init, list, test, m, max]]

Note: These functions were introduced as built-ins in Version 12.2

$\endgroup$
2
  • $\begingroup$ Seth, regarding your comments: indeed, I did not implement the capability to use m most recent results. I this is really necessary, then your implementation is likely a way to go, since Fold can not be used to implement this. As to the complexity - I don't think most users have to reimplement it themselves - they could as well come to this page and pick whichever implementation they like the most :). $\endgroup$ Commented Feb 7, 2013 at 8:40
  • $\begingroup$ These seem still to be missing. Is there a ready explanation of why? $\endgroup$
    – Alan
    Commented Sep 9, 2017 at 5:22

4 Answers 4

27
$\begingroup$

Implementation

Here are my versions. I will start with FoldWhile:

Clear[dressInCtr];
dressInCtr[test_, max_] := 
   Module[{ctr = 0}, (++ctr <= max ) && test[##] &]

Clear[FoldWhile];
FoldWhile[f_, test_, start_, secargs_List, max_Integer] :=
   FoldWhile[f, dressInCtr[test, max], start, secargs];

FoldWhile[f_, test_, start_, secargs_List] :=
  Module[{last = start},
    Fold[
      If[test[##], last = f[##], Return[last, Fold]] &, 
      start, 
      secargs]];

The FoldWhileList is a bit more involved:

Clear[FoldWhileList];
FoldWhileList[f_, test_, start_, secargs_List, max_Integer] :=
   FoldWhileList[f, dressInCtr[test, max], start, secargs];
FoldWhileList[f_, test_, start_, secargs_List] :=
Module[{tag},
   If[# === {}, {start}, Prepend[First@#, start]] &@
    Reap[
      Fold[
        If[test[##], Sow[f[##],tag], Return[Null, Fold]] &, 
        start, 
        secargs], 
      _, #2 &][[2]]]

Examples

Here are some examples:

FoldWhileList[Plus,#2<5&,0,Range[30]]

(* {0,1,3,6,10}  *)

FoldWhileList[Plus,#2<5&,0,Range[30],3]

(* {0,1,3,6} *)

FoldWhile[Plus,#2<5&,0,Range[30]]

(* 10  *)

FoldWhile[Plus,#2<5&,0,Range[30],3]

(* 6 *)

Remarks

I chose to use Fold itself as an economical way to implement FoldWhile and FoldWhileList. It helped that the two-argument version of Return (undocumented) could be used here. I also found it simplest to implement the extended form with a fifth parameter giving maximal number of iterations, by dressing the test criteria in a closure, which is done via a closure generator function dressInCtr. This also seems to be a good illustration of the usefulness of closures.

$\endgroup$
16
  • $\begingroup$ Didn't know Return had a second argument, odd thing to leave out of the documentation. $\endgroup$
    – ssch
    Commented Feb 5, 2013 at 20:13
  • 2
    $\begingroup$ @ssch Agree. This second argument business is explained very well in this excellent answer by Rojo. That answer deserves many more upvotes IMO. $\endgroup$ Commented Feb 5, 2013 at 20:18
  • $\begingroup$ Got 6 upvotes on that answer today. Thanks ;) $\endgroup$
    – Rojo
    Commented Feb 5, 2013 at 22:08
  • $\begingroup$ Btw, making FoldWhileList using FoldList with a modified function that sows the result would be as efficient? $\endgroup$
    – Rojo
    Commented Feb 5, 2013 at 22:09
  • $\begingroup$ @Rojo Welcome :-). Re: as efficient: memory-wise, no, since Sow will have to store those intermediate results internally. This, plus the fact that I can make the code simpler, was my motivation to implement it separately. As for run-time efficiency, a bit less efficient too, but probably not much so. $\endgroup$ Commented Feb 5, 2013 at 22:19
8
$\begingroup$

These are the first methods that came to mind. I'll have to leave comparing them to the other answers for later.

FoldWhile[f_, start_, rest_, test_] :=
 Module[{g},
   g[_, x_?test] := x;
   g[last_, _] := Return[last, Fold];
   Fold[# ~g~ f@## &, start, rest]
 ]

FoldWhile[Plus, 0, Range@100, # < 30 &]
28
FoldWhileList[f_, start_, rest_, test_] :=
 Module[{bag = Internal`Bag[start], g},
  g[x_?test] := (Internal`StuffBag[bag, x]; x);
  g[else_] := Return[Null, Fold];
  Fold[g @ f @ ## &, start, rest];
  Internal`BagPart[bag, All]
 ]

FoldWhileList[Plus, 0, Range@100, # < 30 &]
{0, 1, 3, 6, 10, 15, 21, 28}
$\endgroup$
1
  • $\begingroup$ I'm curious too as to how they all perform, but don't take my answer too seriously $\endgroup$
    – Rojo
    Commented Feb 6, 2013 at 9:23
7
$\begingroup$

Just FYI, Mathematica 12.2 introduces FoldWhile and FoldWhileList with the intended usages.

$\endgroup$
6
$\begingroup$
foldWhile[function_, check_, x_, list_, m_: Infinity] :=
 Module[{counter = 0, out, restart, newValue, result = x, 
   max = Min[m, Length@list]},
  Label[restart];
  ++counter;
  newValue = list[[counter]];
  If[! check[result, newValue] || counter >= max, Goto[out]];
  result = function[result, newValue];
  Goto[restart];
  Label[out];
  result
  ]

Another one

foldWhile[function_, check_, x_, list_, m_: Infinity] :=
 Module[{max = Min[m, Length@list]},
  (Composition @@ list~Take~max)[#][x] //. {
    res_[Except[#, next_][rest_]][val_] /; check[res, next] :> 
     function[val, res][rest][next],
    res_[_][val_] :> function[val, res]
    }
  ]

For both

foldWhileList[f_, test_, start_, rest___] := Module[{tag}, Reap[
    foldWhile[
     Sow[f@##, tag] &, test, Sow[start, tag], rest], tag][[-1, 1]]]
$\endgroup$
5
  • $\begingroup$ @Leonid, ironically, this beats yours for long lists that test out early. Probably you are making a copy of the list in your solution? $\endgroup$
    – Rojo
    Commented Feb 6, 2013 at 1:30
  • $\begingroup$ No time to benchmark now, but no, I don't make a copy, at least I don't see any obvious place where I do. Will look into that later. $\endgroup$ Commented Feb 6, 2013 at 15:26
  • $\begingroup$ Please take a look at Seth's now deleted post further down. He had a comment for you. $\endgroup$
    – rm -rf
    Commented Feb 7, 2013 at 6:15
  • $\begingroup$ @rm-rf does it work for you? I just tried it with the 2 examples in Leonid's answer and it worked $\endgroup$
    – Rojo
    Commented Feb 7, 2013 at 11:53
  • $\begingroup$ Sorry, I haven't tried it yet... been a bit occupied with other things (I haven't fixed my zero rows removal answer either!) $\endgroup$
    – rm -rf
    Commented Feb 7, 2013 at 20:05

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.