37

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

bbgodfrey
  • 61,439
  • 17
  • 89
  • 156
Seth Chandler
  • 3,132
  • 14
  • 25
  • 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 :). – Leonid Shifrin Feb 07 '13 at 08:40
  • These seem still to be missing. Is there a ready explanation of why? – Alan Sep 09 '17 at 05:22

4 Answers4

27

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.

Leonid Shifrin
  • 114,335
  • 15
  • 329
  • 420
  • Didn't know Return had a second argument, odd thing to leave out of the documentation. – ssch Feb 05 '13 at 20:13
  • 2
    @ssch Agree. This second argument business is explained very well in this excellent answer by Rojo. That answer deserves many more upvotes IMO. – Leonid Shifrin Feb 05 '13 at 20:18
  • Got 6 upvotes on that answer today. Thanks ;) – Rojo Feb 05 '13 at 22:08
  • Btw, making FoldWhileList using FoldList with a modified function that sows the result would be as efficient? – Rojo Feb 05 '13 at 22:09
  • @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. – Leonid Shifrin Feb 05 '13 at 22:19
  • I actually meant to ask using FoldWhile and not FoldList, my bad – Rojo Feb 05 '13 at 22:22
  • @Rojo That's a cool idea! It won't be as efficient alas, due to assignments to last (main part) and extra function invocation. So, while I like this from the design perspective, I would probably still keep my version unchanged, for efficiency. Of course, for computationally-intensive functions this should not matter much. – Leonid Shifrin Feb 05 '13 at 22:25
  • @Rojo I see that the two of you implemented a more advanced syntax than I did. I'll have to return to that later. I'm curious to know how my stripped down version performs compared to each of yours, if you've tested it. (That too will have to wait to later for me.) – Mr.Wizard Feb 06 '13 at 02:35
  • @LeonidShifrin Is dressInCtr really a closure (in the sense that variables are bound to the block of executing code) or is it more the case that ctr$nnn is accessible as a global variable outside of module? Also, if I do Names["ctr*"] after the examples I get {"ctr", "ctr$"} so the Temporary attribute of ctr$nnn seems to be working (to some extent). (+1 of course:) – Ajasja Feb 06 '13 at 11:47
  • @Ajasja It is a closure.The fact that in principle one can access variables like ctr$nnn from the top-level is due to the imperfect emulation of lexical scoping by Mathematica's symbol renaming mechanism. Normally, one would not use such variable names, so for all practical purposes this should be ok. Also, if you consider other languages (e.g. javascript), the variables from enclosing environment remain accessible / modifyable from within a returned function (closure) - they are just not available at the top-level. As for Names - yes, Temporary works, because the variables are ... – Leonid Shifrin Feb 06 '13 at 12:02
  • @Ajasja ... enclosed in pure functions, so as soon as the main function (the one using that closure) executes, the garbage collector comes to those variables. – Leonid Shifrin Feb 06 '13 at 12:03
  • @LeonidShifrin Thanks I think I understand! Any idea why {"ctr", "ctr$"} leak into the global contest? Even if I do Names["test*"]; Module[{test = 1}, test + 1]; Names["test*"]; I get {}; 2; {test} (new lines have to be used instead of ; ) – Ajasja Feb 06 '13 at 12:11
  • @Ajasja I think this (leaking) is an artifact of parsing, since the code is parsed and symbols like ctr created before the binding / variable renaming takes place. As for $ctr, this one is probably created at some intermediate stage in the implementation of Module - perhaps part of this implementation is done in Mathematica code (but not the top-level code). I would actually consider the leaking of ctr$ as a borderline bug, albeit not a very important one. – Leonid Shifrin Feb 06 '13 at 12:40
  • One could use Catch[]/Throw[] for implementing FoldWhile[] as well, I suppose. I mean, the docs do hint on this construction... – J. M.'s missing motivation Feb 07 '13 at 03:04
  • @LeonidShifrin Please make a note of Seth's (now deleted) post down. He had a few comments for you. – rm -rf Feb 07 '13 at 06:14
  • @J.M. Sure, this is a possibility. I usually prefer to not use Catch-Throw when softer methods (such as Return) are sufficient. – Leonid Shifrin Feb 07 '13 at 08:35
8

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}
Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
7

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

kglr
  • 394,356
  • 18
  • 477
  • 896
vapor
  • 7,911
  • 2
  • 22
  • 55
6
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]]]
Rojo
  • 42,601
  • 7
  • 96
  • 188
  • @Leonid, ironically, this beats yours for long lists that test out early. Probably you are making a copy of the list in your solution? – Rojo Feb 06 '13 at 01:30
  • 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. – Leonid Shifrin Feb 06 '13 at 15:26
  • Please take a look at Seth's now deleted post further down. He had a comment for you. – rm -rf Feb 07 '13 at 06:15
  • @rm-rf does it work for you? I just tried it with the 2 examples in Leonid's answer and it worked – Rojo Feb 07 '13 at 11:53
  • Sorry, I haven't tried it yet... been a bit occupied with other things (I haven't fixed my zero rows removal answer either!) – rm -rf Feb 07 '13 at 20:05