5

I need to break my for loop in case of an thrown message and append s to the result list. Breaking the loop works, but my result is empty. I can not use Table[] because it does not support Break[].

EDIT: s has the form of list, so I get a Table structure for result

EDIT 2: AppendTo[] yields empty result too.

result = {};
For[i = 0, i < 6, i++, 
 s = {Quiet[
    Check[If[i == 3, Message[FindRoot::jsing, x, 1], i], "Nan", 
     FindRoot::jsing]]};
 If[s == "Nan", Break[], AppendTo[result, s]];
 ]
result

yields:

{}

Any help appreciated.

István Zachar
  • 47,032
  • 20
  • 143
  • 291
Martin Scherer
  • 377
  • 1
  • 3
  • 10

4 Answers4

16

The reason Append[] isn't working in your loop is that it returns a new list with your new element added to the end.

If you want to change the list in place, you should use AppendTo[] instead.

sblom
  • 6,453
  • 3
  • 28
  • 45
  • thx, now I know why Append in loops is evil! – Martin Scherer Apr 19 '12 at 17:34
  • 3
    @MartinScherer Oh no, the main reason for that (Append being evil in loops) is not it. It is that the full list is copied to append a single element. – Leonid Shifrin Apr 19 '12 at 17:35
  • calling copy constructors all the time :) – Martin Scherer Apr 19 '12 at 17:40
  • @LeonidShifrin, which suggests an interesting follow-up question. Is AppendTo's implementation equally eager to copy things repeatedly? I can imagine an implementation where the Append version is forced to copy with every operation, but where AppendTo is implemented much like C++'s std::vector and only has to copy some of the time instead of on every step. I just ran some Timings and it looks like their behavior is almost identical. – sblom Apr 19 '12 at 17:48
  • @sblom I have once implemented a dynamic array structure in Mathematica, which does have this behavior (not that it is very hard). Didn't have time to polish it and publish, but perhaps this is not a bad idea. It is a little at odds with idiomatic Mathematica programming though, since it has a state. I think, the real reason why it is not that much useful in practice, is that most of the time, people don't want random access to the prepared list (just the ability to add in constant time) - and used linked lists. and when they do, it will be too slow anyways (top-level implementation). – Leonid Shifrin Apr 19 '12 at 17:54
  • @LeonidShifrin While we're on this topic, I mostly use old = {new, old} to grow a list, but do you have any insights on the performance of Join? For example, {new} ~Join~ old vs. old ~Join~ {new}? If it's better than Append and only marginally worse than linked lists, then it provides a way to add quickly and have easy access to an arbitrary element. I use this at times for moderately sized lists and found it quite convenient. However, I don't know (and haven't spent time poking) how Join works and whether it copies the entire list too... thoughts? – rm -rf Apr 19 '12 at 18:17
  • @R.M. Join surely copies lists, it has no other choice :). So, it is no better than Append, at least conceptually (I do not remember the results of micro-benchamrking and am actually being increasingly annoyed by the fact that the language encourages this massive waste of time and effort). Using linked lists is different because at every given iteration, there are only two elements in the list - a newly added one and a pointer to the old list. The fact that pointers are not available to us explicitly, is irrelevant - the point is that there is no massive copying. – Leonid Shifrin Apr 19 '12 at 18:54
  • @R.M. Also, you can not provide both fast addition and fast lookup (random access) without more sophisticated data structures, such as hash tables or binary search trees. You can sort of do this (for the average time of addition) with dynamic arrays, but as I said, this does not make much sense for the top-level code anyways - since if you need random access then chances are that your top-level code using it will be slow. – Leonid Shifrin Apr 19 '12 at 19:00
  • 2
    @sblom I noticed that I did not really answer your question. The answer is yes, AppendTo is just as bad in this regard as Append. Moreover, you can see that AppendTo has been actually implemented with Set and Append, by using On[Set], and then say a = Range[5]; AppendTo[a, 6]. – Leonid Shifrin Apr 19 '12 at 19:31
  • @LeonidShifrin Thanks for the very clear explanation :) – rm -rf Apr 19 '12 at 21:34
5

How about:

result = {};
For[i = 0, i < 6, i++, 
 s = Check[If[i == 3, Message[FindRoot::jsing, x, 1], i], "Nan", 
   FindRoot::jsing];
 Print[s];
 If[s == "Nan", Break[], result = {result, s}];]
Flatten[result]

or

result = {};
For[i = 0, i < 6, i++, 
 s = Check[If[i == 3, Message[FindRoot::jsing, x, 1], i], "Nan", 
   FindRoot::jsing];
 Print[s];
 If[s == "Nan", Break[], AppendTo[result, {s, dim}]];]
result
4

You may not be able to use Table with Break[] but you can use Do:

Break[]
    exits the nearest enclosing Do, For or While.

I also prefer Sow and Reap for speed and flexibility.

Reap[
  Do[
    s = Check[If[i == 3, Message[FindRoot::jsing, x, 1], i], "Nan", FindRoot::jsing];
    If[s == "Nan", Break[], Sow@s],
    {i, 0, 5}
  ]
][[2, 1]]
{0, 1, 2}
Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371
2

In an effort to "say no to loops", here is an alternative that substitutes For and Break with Fold and a Throw/Catch construct. There might be another alternative using FoldList and fewer uses of Flatten, but it's late at night here and I wasn't able to get that quite working. This works:

Catch@Fold[
  With[{chk = 
      Check[If[#2 == 3, Message[FindRoot::jsing, x, 1], #2], "Nan", 
       FindRoot::jsing]},
    If[chk == "Nan", Throw[Flatten@{#1, #2}], 
     Flatten@{#1, chk}]] &, {}, Range[6] ]

FindRoot::jsing: Encountered a singular Jacobian at the point x = 1. Try perturbing the initial point(s). >>

{1, 2, 3}

I think approaches such as this are preferable to For loops, since the latter tends to result in iterator variables (in this case, i) that remain globally defined, which is probably not what you want.

Verbeia
  • 34,233
  • 9
  • 109
  • 224